diff --git a/.github/workflows/build-on-push.yml b/.github/workflows/build-on-push.yml index 9720316dd2..c1409c50b1 100644 --- a/.github/workflows/build-on-push.yml +++ b/.github/workflows/build-on-push.yml @@ -7,59 +7,37 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - postgresql: ['9.6','14'] - services: - postgres: - image: postgres:${{ matrix.postgresql }} - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - ports: - - 5432:5432 - # needed because the postgres container does not provide a healthcheck - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + postgresql: ['10','15'] steps: - - uses: actions/checkout@v1 - - uses: actions/cache@v1 + - uses: actions/checkout@v3 + - uses: actions/cache@v3 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: | ${{ runner.os }}-gradle- - - uses: actions/cache@v1 + - uses: actions/cache@v3 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradlew') }} restore-keys: | ${{ runner.os }}-gradlew- - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 11 - - name: Set up DB - run: | - sudo apt-get install --yes postgresql-client - psql -h 127.0.0.1 -U postgres postgres -c 'create database alfio;' -f src/test/resources/init-db-user.sql - env: - PGPASSWORD: postgres + java-version: 17 + distribution: temurin - name: Build with Gradle - run: ./gradlew build distribution jacocoTestReport -Dspring.profiles.active=travis -Ddbenv=PGSQL-TRAVIS -Dpgsql${{ matrix.postgresql }} + run: ./gradlew dist jacocoTestReport -Dpgsql.version=${{ matrix.postgresql }} - name: upload to sonarcloud if: ${{ github.repository == 'alfio-event/alf.io' && matrix.postgresql == '14'}} run: ./gradlew sonarqube env: SONARCLOUD_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -# - name: upload-to-coveralls -# if: matrix.postgresql == '13' # run only once -# run: ./gradlew coveralls -# env: -# COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} - name: 'Upload Build' - if: ${{ github.repository == 'alfio-event/alf.io' && matrix.postgresql == '9.6'}} - uses: actions/upload-artifact@v2 + if: ${{ github.repository == 'alfio-event/alf.io' && matrix.postgresql == '10'}} + uses: actions/upload-artifact@v3 with: name: dist path: build @@ -71,23 +49,26 @@ jobs: name: Push dev image steps: - name: Download artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: dist + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - name: Configure Docker - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Login to Container Registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@v3.x + uses: rlespinasse/github-slug-action@v4.4.1 - name: Push Docker image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v4 with: context: ./dockerize tags: | ghcr.io/alfio-event/alf.io/dev-${{ env.GITHUB_REF_SLUG }}:latest + platforms: linux/amd64,linux/arm64 push: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0e0dee3c6c..39449df9d2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,28 +13,24 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 11 + java-version: 17 + distribution: temurin - name: Build with Gradle run: ./gradlew build -x test @@ -51,4 +47,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 5ed4d1caf0..d0e052cc25 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -9,26 +9,27 @@ jobs: if: github.repository == 'alfio-event/alf.io' strategy: matrix: - browser: ['chrome', 'firefox', 'safari', 'ie11'] + browser: ['chrome', 'firefox', 'safari'] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/cache@v1 + - uses: actions/checkout@v3 + - uses: actions/cache@v3 with: path: ~/.gradle/caches key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} restore-keys: | ${{ runner.os }}-gradle- - - uses: actions/cache@v1 + - uses: actions/cache@v3 with: path: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradlew') }} restore-keys: | ${{ runner.os }}-gradlew- - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 with: - java-version: 11 + java-version: 17 + distribution: temurin - name: 'BrowserStack Env Setup' uses: 'browserstack/github-actions/setup-env@master' with: diff --git a/.gitignore b/.gitignore index 0fda5edfaa..5274c4c67d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,6 @@ out/ alfio-itest .gradletasknamecache public/ +!frontend/projects/public node_modules/ lsp diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000000..077c04ba59 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=17.0.3-zulu diff --git a/README.md b/README.md index d0ecd788e8..76f3f914b3 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ The open source ticket reservation system. ## Warning As the work for Alf.io [v2](https://github.com/alfio-event/alf.io/milestones) has started, this branch may contain **unstable** and **untested** code. -If you want to build and deploy alf.io by yourself, we strongly suggest you to use the [2.0-M3-maintenance](https://github.com/alfio-event/alf.io/tree/2.0-M3-maintenance) branch, as it contains production-ready code. +If you want to build and deploy alf.io by yourself, we strongly suggest you to use the [2.0-M4-maintenance](https://github.com/alfio-event/alf.io/tree/2.0-M4-maintenance) branch, as it contains production-ready code. ## Prerequisites -You should have installed Java version **11** (e.g. [Oracle's](http://www.oracle.com/technetwork/java/javase/downloads/index.html), [OpenJDK](http://openjdk.java.net/install/), or any other distribution) to build and run alf.io. Please note that for the build process the JDK is required. +You should have installed Java version **17** (e.g. [Oracle's](http://www.oracle.com/technetwork/java/javase/downloads/index.html), [OpenJDK](http://openjdk.java.net/install/), or any other distribution) to build and run alf.io. Please note that for the build process the JDK is required. -Postgresql version 9.6 or later. +Postgresql version 10 or later. Additionally, the database user that creates and uses the tables should not be a "SUPERUSER", or else the row security policy checks will not be applied. @@ -46,7 +46,7 @@ You must specify a project property at the command line, such as ``` The local "bootRun" task has the following prerequisites: -- a PostgreSQL (version 9.6 or later) instance up and running on localhost:5432 +- a PostgreSQL (version 10 or later) instance up and running on localhost:5432 - a _postgres_ user having a password: _password_ - a database named _alfio_ @@ -153,10 +153,10 @@ However, if you decide to do so, then you need to make a couple of changes: docker build -t alfio/alf.io ./build/dockerize ``` -### About the included AppleWWDRCA.cer +### About the included AppleWWDRCAG4.cer -The certificate at src/main/resources/alfio/certificates/AppleWWDRCA.cer has been imported for https://github.com/ryantenney/passkit4j#usage functionality. -It will expire the 02/07/23 (as https://www.apple.com/certificateauthority/). +The certificate at src/main/resources/alfio/certificates/AppleWWDRCAG4.cer has been imported for https://github.com/ryantenney/passkit4j#usage functionality. +It will expire the 2030-10-12 (YYYY-MM-DD - as of https://www.apple.com/certificateauthority/). ## Available spring profiles: @@ -174,7 +174,7 @@ This project exists thanks to all the people who contribute. ### Translation Contributors (POEditor) -A big "Thank you" goes also to our translators, who help us on [POEditor](https://poeditor.com/join/project/ttBYTmPYdr): +A big "Thank you" goes also to our translators, who help us on [POEditor](https://github.com/alfio-event/alf.io/tree/master/src/main/resources/alfio/i18n): (we show the complete name/profile only if we have received explicit consent to do so) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..014995f902 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security Policy + +## Supported Versions + +We support the latest major release, which is at the moment **2.0-M4**. +In case of direct or transitive vulnerability in that code, we'll release a new version as soon as possible. +Please follow us on [Twitter](https://twitter.com/alfio_event) for release announcements, and keep your alf.io updated! + +| Version | Supported | +| ------- | ------------------ | +| 2.0-M4 | :white_check_mark: | +| 2.0-M3 | :x: | +| 2.0-M2 | :x: | +| 2.0-M1 | :x: | + +## Note about the master branch + +We consider the "master" branch to be always a "Work in Progress", and as such it might contain vulnerabilities. +Before each release we'll run an additional scan on [SonarCloud](https://sonarcloud.io/summary/overall?id=alfio-event_alf.io) and fix all the security-releated findings. +That branch should not be deployed in production. If you do that, you're on your own. + + +## Reporting a Vulnerability + +Please reach out via email to `security @ alf.io` (remove spaces), we'll reply as soon as possible. Thank you for sharing responsibly! diff --git a/build.gradle b/build.gradle index 3f8d227ba7..e5e0f6af5a 100644 --- a/build.gradle +++ b/build.gradle @@ -13,11 +13,9 @@ import java.time.format.DateTimeFormatter buildscript { dependencies { - classpath 'com.opentable.components:otj-pg-embedded:0.13.3' - classpath 'org.postgresql:postgresql:42.2.20' + classpath 'org.postgresql:postgresql:42.5.1' //this is for processing the index.html at compile time - classpath "com.github.alfio-event:alf.io-public-frontend:$alfioPublicFrontendVersion" - classpath "ch.digitalfondue.jfiveparse:jfiveparse:0.9.0" + classpath "ch.digitalfondue.jfiveparse:jfiveparse:1.0.0" // } @@ -26,24 +24,21 @@ buildscript { url "https://plugins.gradle.org/m2/" } mavenCentral() - maven { - url "https://jitpack.io" - } } } plugins { - id 'io.freefair.lombok' version '5.3.3.3' + id 'io.freefair.lombok' version '6.6.1' id 'java' id 'idea' - id 'org.kordamp.gradle.jacoco' version '0.45.0' - id 'com.github.ben-manes.versions' version '0.38.0' + id 'org.kordamp.gradle.jacoco' version '0.48.0' + id 'com.github.ben-manes.versions' version '0.44.0' id 'com.github.hierynomus.license' version '0.16.1' - id 'net.researchgate.release' version '2.8.1' - id 'org.springframework.boot' version '2.4.5' - id 'org.sonarqube' version '3.3' - id 'net.ltgt.errorprone' version '2.0.1' - id 'com.github.node-gradle.node' version '3.1.0' + id 'net.researchgate.release' version '3.0.2' + id 'org.springframework.boot' version '2.7.7' + id 'org.sonarqube' version '3.5.0.2730' + id 'net.ltgt.errorprone' version '2.0.2' + id 'com.github.node-gradle.node' version '3.5.1' } apply plugin: 'java' @@ -52,13 +47,17 @@ apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'project-report' +// MJML email templates translations to HTML +node { + download = true +} //as pointed out by @facundofarias, we should validate minimum javac version -task validate { +tasks.register('validate') { //check JDK version def javaVersion = JavaVersion.current() - if (!javaVersion.isJava11Compatible()) { - throw new GradleException("A Java JDK 11+ is required to build the project.") + if (!javaVersion.isCompatibleWith(JavaVersion.VERSION_17)) { + throw new GradleException("A Java JDK 17+ is required to build the project.") } } @@ -100,20 +99,17 @@ configurations { repositories { mavenLocal() mavenCentral() - maven { - url "https://jitpack.io" - } } dependencies { implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" - implementation 'com.auth0:java-jwt:3.18.2' + implementation 'com.auth0:java-jwt:4.2.1' implementation "com.fasterxml.jackson.core:jackson-core" implementation "com.fasterxml.jackson.core:jackson-databind" implementation "org.springframework.boot:spring-boot-properties-migrator", { exclude module : 'spring-boot-starter-logging' } - implementation 'org.springframework.session:spring-session:1.3.5.RELEASE' + implementation 'org.springframework.session:spring-session-jdbc' implementation "ch.digitalfondue.npjt-extra:npjt-extra:2.0.4" implementation "com.samskivert:jmustache:1.15" implementation "javax.mail:mail:1.5.0-b01" @@ -121,50 +117,48 @@ dependencies { /**/ implementation 'com.openhtmltopdf:openhtmltopdf-core:1.0.10' implementation 'com.openhtmltopdf:openhtmltopdf-pdfbox:1.0.10' - implementation "ch.digitalfondue.jfiveparse:jfiveparse:0.9.0" + implementation 'ch.digitalfondue.jfiveparse:jfiveparse:1.0.0' /**/ - implementation "com.google.zxing:core:3.4.1" - implementation "com.google.zxing:javase:3.4.1" + implementation 'com.google.zxing:core:3.5.1' + implementation 'com.google.zxing:javase:3.5.1' implementation "org.flywaydb:flyway-core" implementation "org.postgresql:postgresql" implementation "com.zaxxer:HikariCP" /* https://www.lunasec.io/docs/blog/log4j-zero-day/ */ - implementation "org.apache.logging.log4j:log4j-api:2.17.1" - implementation "org.apache.logging.log4j:log4j-core:2.17.1" - implementation "org.apache.logging.log4j:log4j-jul:2.17.1" - implementation "org.apache.logging.log4j:log4j-slf4j-impl:2.17.1" + implementation 'org.apache.logging.log4j:log4j-api:2.19.0' + implementation 'org.apache.logging.log4j:log4j-core:2.19.0' + implementation 'org.apache.logging.log4j:log4j-jul:2.19.0' + implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0' /**/ - implementation "com.stripe:stripe-java:20.50.0" - implementation 'com.paypal.sdk:checkout-sdk:1.0.5' - implementation 'com.google.code.gson:gson:2.8.9' - implementation 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.1', { + implementation "com.stripe:stripe-java:22.4.0" + implementation 'com.paypal.sdk:checkout-sdk:2.0.0' + implementation 'com.google.code.gson:gson:2.10' + implementation 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2', { exclude module: 'gson' } implementation "org.apache.commons:commons-lang3:3.12.0" - implementation "org.apache.commons:commons-text:1.9" - implementation 'com.opencsv:opencsv:5.5.2' + implementation 'com.opencsv:opencsv:5.7.1' implementation 'commons-codec:commons-codec:1.15' implementation 'net.sf.biweekly:biweekly:0.6.6' implementation 'com.atlassian.commonmark:commonmark:0.17.0' implementation 'com.atlassian.commonmark:commonmark-ext-gfm-tables:0.17.0' implementation 'com.ryantenney.passkit4j:passkit4j:2.0.1' implementation 'com.github.ben-manes.caffeine:caffeine' - implementation 'com.github.scribejava:scribejava-core:5.0.0' + implementation 'com.github.scribejava:scribejava-core:8.3.3' implementation 'ch.digitalfondue.vatchecker:vatchecker:1.5.0' implementation 'ch.digitalfondue.basicxlsx:basicxlsx:0.5.1' implementation 'org.imgscalr:imgscalr-lib:4.2' implementation 'org.mozilla:rhino-runtime:1.7.13' - - implementation "com.github.alfio-event:alf.io-public-frontend:$alfioPublicFrontendVersion" + implementation 'com.google.auth:google-auth-library-oauth2-http:1.3.0' compileOnly "javax.servlet:javax.servlet-api:4.0.1" testImplementation "javax.servlet:javax.servlet-api:4.0.1" - testImplementation 'org.testcontainers:testcontainers:1.16.2' - testImplementation 'org.testcontainers:postgresql:1.16.2' - testImplementation 'org.testcontainers:junit-jupiter:1.16.2' + testImplementation 'org.testcontainers:testcontainers:1.17.6' + testImplementation 'org.testcontainers:postgresql:1.17.6' + testImplementation 'org.testcontainers:junit-jupiter:1.17.6' testImplementation "org.springframework.boot:spring-boot-starter-test", { exclude module : 'spring-boot-starter-logging' } @@ -197,6 +191,10 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-engine" testImplementation "org.junit.platform:junit-platform-engine" + testImplementation "org.mockito:mockito-inline:4.5.1" + + testImplementation "org.springdoc:springdoc-openapi-ui:1.6.14" + testImplementation "org.openapitools.openapidiff:openapi-diff-core:2.0.1" providedCompile "org.springframework.boot:spring-boot-starter-jetty", { exclude group: "org.eclipse.jetty.websocket", module: "websocket-server" @@ -209,16 +207,16 @@ dependencies { exclude module : 'spring-boot-starter-logging' } - implementation "org.joda:joda-money:1.0.1" + implementation "org.joda:joda-money:1.0.3" - testImplementation 'org.mock-server:mockserver-netty:5.11.2', { + testImplementation 'org.mock-server:mockserver-netty:5.13.2', { exclude group: 'org.mozilla', module: 'rhino' } annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" - testImplementation 'org.seleniumhq.selenium:selenium-java:4.1.1' + testImplementation 'org.seleniumhq.selenium:selenium-java:4.7.2' - errorprone('com.google.errorprone:error_prone_core:2.4.0') + errorprone('com.google.errorprone:error_prone_core:2.10.0') } // -- license configuration @@ -258,30 +256,7 @@ processResources { gradleProperties.withReader { properties.load(it) } properties['alfio.version'] = project.version properties['alfio.build-ts'] = ZonedDateTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_ZONED_DATE_TIME) - properties['alfio.frontend.version'] = alfioPublicFrontendVersion gradleProperties.withWriter { properties.store(it, null) } - - - // - // alf.io public frontend: read index.html rewrite path for css/js - - final resource = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/resources/webjars/alfio-public-frontend/$alfioPublicFrontendVersion/alfio-public-frontend/index.html") - final indexDoc = new Parser().parse(new InputStreamReader(resource, StandardCharsets.UTF_8)) - - final basePath = "webjars/alfio-public-frontend/$alfioPublicFrontendVersion/alfio-public-frontend/" - NodeMatcher scriptNodes = Selector.select().element("script").toMatcher() - - indexDoc.getAllNodesMatching(scriptNodes).stream().forEach({ - it.setAttribute("src", basePath + it.getAttribute("src")) - }) - - NodeMatcher cssNodes = Selector.select().element("link").attrValEq("rel", "stylesheet").toMatcher(); - indexDoc.getAllNodesMatching(cssNodes).stream().forEach({ - it.setAttribute("href", basePath + it.getAttribute("href")) - }) - - final alfioPublicIndex = new File((File) it.destinationDir, "alfio-public-frontend-index.html") - alfioPublicIndex.write(HtmlSerializer.serialize(indexDoc), "UTF-8", false) } } @@ -293,7 +268,8 @@ compileTestJava { 'AlmostJavadoc', 'MissingSummary', 'EscapedEntity', - 'EmptyBlockTag' + 'EmptyBlockTag', + 'SameNameButDifferent' ) } @@ -308,7 +284,9 @@ compileJava { 'AlmostJavadoc', 'MissingSummary', 'EscapedEntity', - 'EmptyBlockTag' + 'EmptyBlockTag', + 'SameNameButDifferent', + 'ReturnValueIgnored' ) } @@ -319,7 +297,6 @@ test { useJUnitPlatform() systemProperties = System.properties systemProperties.remove("java.endorsed.dirs") - jvmArgs("--illegal-access=warn") testLogging { events "failed" exceptionFormat "full" @@ -327,6 +304,9 @@ test { } } +springBoot { + mainClass = 'alfio.config.SpringBootLauncher' +} bootRun { def externalConfig = new File("./custom.jvmargs") @@ -337,33 +317,16 @@ bootRun { "-Ddatasource.username=${project.datasourceUsername}", "-Ddatasource.password=${project.datasourcePassword}", "-Dalfio.version=${project.version}", - "-Dalfio.build-ts=${ZonedDateTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)}", - "-Dalfio.frontend.version=$alfioPublicFrontendVersion" + "-Dalfio.build-ts=${ZonedDateTime.now(ZoneId.of("UTC")).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)}" ] if(externalConfig.exists()) { opts += externalConfig.readLines() } jvmArgs = opts - mainClass = 'alfio.config.SpringBootLauncher' -} - -bootWar { - mainClassName = 'alfio.config.SpringBootLauncher' - classifier = 'boot' - - def bowerDir = "resources/bower_components" - def excludesFile = new File("./lib_exclude") - if(excludesFile.exists()) { - exclude(excludesFile.readLines().collect({ bowerDir + it })) - } } // -- code-coverage -jacoco { - toolVersion = '0.8.7' -} - jacocoTestReport { group = 'Reporting' description = 'Generate Jacoco coverage reports after running tests.' @@ -377,19 +340,47 @@ jacocoTestReport { } } -task dockerize(type: Copy) { +tasks.register('dockerize', Copy) { from 'src/main/dist/Dockerfile' into "${buildDir}/dockerize" filter(ReplaceTokens, tokens: [ALFIO_VERSION: project.version]) } -task distribution(type: Copy) { +tasks.register('frontendNpmInstall', NpmTask) { + args = ['--prefix', "${project.projectDir}/frontend", 'install'] +} + +tasks.register('frontendBuild', NpmTask) { + dependsOn frontendNpmInstall + args = ['--prefix', "${project.projectDir}/frontend", 'run', 'build'] + outputs.dir("${project.projectDir}/frontend/dist") +} + +clean.doFirst { + delete "${project.projectDir}/frontend/dist" +} + +tasks.register('publicFrontendIndexTransform', FrontendIndexTransformTask) { + dependsOn frontendBuild + basePath.set("frontend-public/") + indexHtml.set(layout.projectDirectory.file("frontend/dist/alfio-public-frontend/index.html")) + indexHtmlTransformed.set(layout.buildDirectory.file("index-transformed/alfio-public-frontend-index.html")) +} + +tasks.register('adminFrontendIndexTransform', FrontendIndexTransformTask) { + dependsOn frontendBuild + basePath.set("frontend-admin/") + indexHtml.set(layout.projectDirectory.file("frontend/dist/alfio-admin-frontend/index.html")) + indexHtmlTransformed.set(layout.buildDirectory.file("index-transformed/alfio-admin-frontend-index.html")) +} + +tasks.register('distribution', Copy) { from zipTree("${project.buildDir}/libs/alfio-${project.version}-boot.war") into "${buildDir}/dockerize" - dependsOn build, dockerize + dependsOn publicFrontendIndexTransform, build, dockerize } -task clever(type: Copy) { +tasks.register('clever', Copy) { from new File(project.buildDir, "libs/alfio-${project.version}-boot.war") rename(new Transformer<String, String>() { @Override @@ -398,31 +389,83 @@ task clever(type: Copy) { } }) into "${project.buildDir}/clevercloud" - dependsOn build + dependsOn publicFrontendIndexTransform, adminFrontendIndexTransform, build } release { buildTasks = ['distribution'] git { - requireBranch = '' - pushToRemote = 'origin' - signTag = true + requireBranch.set('') + pushToRemote.set('origin') + signTag.set(true) } } -// see https://github.com/freefair/gradle-plugins/issues/31#issuecomment-475355674 -// since we have a custom lombok.config configuration file, we disable automatic override -generateLombokConfig.enabled = false -// MJML email templates translations to HTML -node { - download = true +bootWar { + dependsOn publicFrontendIndexTransform, adminFrontendIndexTransform + archiveClassifier.set('boot') + from(tasks.named("frontendBuild")) { + into 'resources' + } + from(tasks.named("publicFrontendIndexTransform")) { + rename 'alfio-public-frontend-index.html', 'WEB-INF/classes/alfio-public-frontend-index.html' + } + from(tasks.named("adminFrontendIndexTransform")) { + rename 'alfio-admin-frontend-index.html', 'WEB-INF/classes/alfio-admin-frontend-index.html' + } + def bowerDir = "resources/bower_components" + def excludesFile = new File("./lib_exclude") + if(excludesFile.exists()) { + exclude(excludesFile.readLines().collect({ bowerDir + it })) + } } -task mjmlToHtml( type: NodeTask, dependsOn: 'npmInstall' ) { - script = file( 'src/main/node/mjmlToHtml.js' ) +// MJML email templates translations to HTML + +tasks.register('mjmlToHtml', NodeTask) { + dependsOn npmInstall + script = file('src/main/node/mjmlToHtml.js') } + // We build HTML templates from MJML source files and then save them under "build/generated/resources" in order to be // included in the final artifact. // TODO should we do the same for plaintext templates? See https://gist.github.com/brasilikum/3cd515bad5541ca6c76873faf10445c2 processResources.dependsOn(mjmlToHtml) sourceSets.main.output.dir file("$buildDir/generated/resources"), builtBy: mjmlToHtml + +// transform index.html +abstract class FrontendIndexTransformTask extends org.gradle.api.DefaultTask { + + @org.gradle.api.tasks.InputFile + abstract org.gradle.api.file.RegularFileProperty getIndexHtml() + + @org.gradle.api.tasks.OutputFile + abstract org.gradle.api.file.RegularFileProperty getIndexHtmlTransformed() + + @org.gradle.api.tasks.Input + abstract Property<String> getBasePath() + + FrontendIndexTransformTask() { + basePath.convention("frontend-public/") + } + + + @org.gradle.api.tasks.TaskAction + def doWork() { + final resource = indexHtml.get().asFile.newInputStream() + final indexDoc = new Parser().parse(new InputStreamReader(resource, StandardCharsets.UTF_8)) + + NodeMatcher scriptNodes = Selector.select().element("script").toMatcher() + + indexDoc.getAllNodesMatching(scriptNodes).stream().forEach({ + it.setAttribute("src", basePath.get() + it.getAttribute("src")) + }) + + NodeMatcher cssNodes = Selector.select().element("link").attrValEq("rel", "stylesheet").toMatcher() + indexDoc.getAllNodesMatching(cssNodes).stream().forEach({ + it.setAttribute("href", basePath.get() + it.getAttribute("href")) + }) + + indexHtmlTransformed.get().asFile.write(HtmlSerializer.serialize(indexDoc), "UTF-8", false) + } +} \ No newline at end of file diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000000..59d9a3a3e7 --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000000..9f4777da7a --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,43 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.vscode/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000000..b06dd9d306 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,27 @@ +# Frontend + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.1. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/frontend/angular.json b/frontend/angular.json new file mode 100644 index 0000000000..b1fad4dcb6 --- /dev/null +++ b/frontend/angular.json @@ -0,0 +1,285 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "common": { + "projectType": "library", + "root": "projects/common", + "sourceRoot": "projects/common/src", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "project": "projects/common/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/common/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "projects/common/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "tsConfig": "projects/common/tsconfig.spec.json", + "polyfills": "projects/common/polyfills.ts" + } + } + } + }, + "public": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "projects/public", + "sourceRoot": "projects/public/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "aot": true, + "outputPath": "dist/alfio-public-frontend", + "index": "projects/public/src/index.html", + "main": "projects/public/src/main.ts", + "polyfills": "projects/public/src/polyfills.ts", + "tsConfig": "projects/public/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "projects/public/src/favicon.ico", + "projects/public/src/assets" + ], + "styles": [ + "projects/public/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/public/node_modules", + "projects/common/style" + ] + }, + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb" + } + ], + "outputHashing": "all", + "fileReplacements": [ + { + "replace": "projects/public/src/environments/environment.ts", + "with": "projects/public/src/environments/environment.prod.ts" + } + ], + "optimization": { + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false + }, + "fonts": true + } + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "proxyConfig": "projects/public/proxy.config.json" + }, + "configurations": { + "production": { + "browserTarget": "public:build:production" + }, + "development": { + "browserTarget": "public:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "public:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": "projects/public/src/polyfills.ts", + "tsConfig": "projects/public/tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + "projects/public/src/favicon.ico", + "projects/public/src/assets" + ], + "styles": [ + "projects/public/src/styles.scss" + ], + "scripts": [] + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } + } + } + }, + "admin": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "projects/admin", + "sourceRoot": "projects/admin/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/alfio-admin-frontend", + "index": "projects/admin/src/index.html", + "main": "projects/admin/src/main.ts", + "polyfills": "projects/admin/src/polyfills.ts", + "tsConfig": "projects/admin/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "projects/admin/src/favicon.ico", + "projects/admin/src/assets" + ], + "styles": [ + "projects/admin/src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "projects/admin/node_modules", + "projects/common/style" + ] + }, + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all", + "fileReplacements": [ + { + "replace": "projects/admin/src/environments/environment.ts", + "with": "projects/admin/src/environments/environment.prod.ts" + } + ], + "optimization": { + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false + }, + "fonts": true + } + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "proxyConfig": "projects/admin/proxy.config.json" + }, + "configurations": { + "production": { + "browserTarget": "admin:build:production" + }, + "development": { + "browserTarget": "admin:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "admin:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": "projects/admin/polyfills.ts", + "tsConfig": "projects/admin/tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + "projects/admin/src/favicon.ico", + "projects/admin/src/assets" + ], + "styles": [ + "projects/admin/src/styles.scss" + ], + "scripts": [] + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } + } + } + } + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000000..a0d24bc389 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,21211 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "@angular/animations": "^14.2.0", + "@angular/common": "^14.2.0", + "@angular/compiler": "^14.2.0", + "@angular/core": "^14.2.0", + "@angular/forms": "^14.2.0", + "@angular/platform-browser": "^14.2.0", + "@angular/platform-browser-dynamic": "^14.2.0", + "@angular/router": "^14.2.0", + "rxjs": "~7.5.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.2.10", + "@angular/cli": "~14.2.10", + "@angular/compiler-cli": "^14.2.0", + "@types/jasmine": "~4.0.0", + "jasmine-core": "~4.3.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.0.0", + "ng-packagr": "^14.2.0", + "typescript": "~4.7.2" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.10.tgz", + "integrity": "sha512-/6YmPrgataj1jD2Uqd1ED+CG4DaZGacoeZd/89hH7hF76Nno8K18DrSOqJAEmDnOWegpSRGVLd0qP09IHmaG5w==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.2.10", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.10.tgz", + "integrity": "sha512-VCeZAyq4uPCJukKInaSiD4i/GgxgcU4jFlLFQtoYNmaBS4xbPOymL19forRIihiV0dwNEa2L694vRTAPMBxIfw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/build-webpack": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@babel/core": "7.18.10", + "@babel/generator": "7.18.12", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.18.10", + "@babel/plugin-transform-async-to-generator": "7.18.6", + "@babel/plugin-transform-runtime": "7.18.10", + "@babel/preset-env": "7.18.10", + "@babel/runtime": "7.18.9", + "@babel/template": "7.18.10", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "14.2.10", + "ansi-colors": "4.1.3", + "babel-loader": "8.2.5", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.9.1", + "cacache": "16.1.2", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.1", + "esbuild-wasm": "0.15.5", + "glob": "8.0.3", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.0.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "mini-css-extract-plugin": "2.6.1", + "minimatch": "5.1.0", + "open": "8.4.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "6.0.1", + "piscina": "3.2.0", + "postcss": "8.4.16", + "postcss-import": "15.0.0", + "postcss-loader": "7.0.1", + "postcss-preset-env": "7.8.0", + "regenerator-runtime": "0.13.9", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.54.4", + "sass-loader": "13.0.2", + "semver": "7.3.7", + "source-map-loader": "4.0.0", + "source-map-support": "0.5.21", + "stylus": "0.59.0", + "stylus-loader": "7.0.0", + "terser": "5.14.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.4.0", + "webpack": "5.74.0", + "webpack-dev-middleware": "5.3.3", + "webpack-dev-server": "4.11.0", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.15.5" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0", + "@angular/localize": "^14.0.0", + "@angular/service-worker": "^14.0.0", + "karma": "^6.3.0", + "ng-packagr": "^14.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=4.6.2 <4.9" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/cacache": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", + "integrity": "sha512-Xx+xPlfCZIUHagysjjOAje9nRo8pRDczQCcXb4J2O0BLtH+xeVue6ba4y1kfJfQMAnM2mkcoMIAyOctlaRGWYA==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.10.tgz", + "integrity": "sha512-h+2MaSY7QSvoJ3R+Hvin21jVCfPGOTLdASIUk4Jmq6J3y5BSku3KSSaV8dWoBOBkFCwQyPQMRjiHoHKLpC1K7g==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1402.10", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/core": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.10.tgz", + "integrity": "sha512-K4AO7mROTdbhQ7chtyQd6oPwmuL+BPUh+wn6Aq1qrmYJK4UZYFOPp8fi/Ehs8meCEeywtrssOPfrOE4Gsre9dg==", + "dev": true, + "dependencies": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/schematics": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.10.tgz", + "integrity": "sha512-MMp31KpJTwKHisXOq+6VOXYApq97hZxFaFmZk396X5aIFTCELUwjcezQDk+u2nEs5iK/COUfnN3plGcfJxYhQA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.2.10", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular/animations": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-14.2.12.tgz", + "integrity": "sha512-gwdnFZkvVUr+enUNfhfCGRGGqNHn1+vTA81apLfHYhJxgjiLUtETc4KTOrQevtDm022pEd+LSrvr8r+7ag+jkw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.12" + } + }, + "node_modules/@angular/cli": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.10.tgz", + "integrity": "sha512-gX9sAKOwq4lKdPWeABB7TzKDHdjQXvkUU8NmPJA6mEAVXvm3lhQtFvHDalZstwK8au2LY0LaXTcEtcKYOt3AXQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "@schematics/angular": "14.2.10", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "debug": "4.3.4", + "ini": "3.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "npm-package-arg": "9.1.0", + "npm-pick-manifest": "7.0.1", + "open": "8.4.0", + "ora": "5.4.1", + "pacote": "13.6.2", + "resolve": "1.22.1", + "semver": "7.3.7", + "symbol-observable": "4.0.0", + "uuid": "8.3.2", + "yargs": "17.5.1" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.2.12.tgz", + "integrity": "sha512-oZunh9wfInFWhNO1P8uoEs/o4u8kerKMhw8GruywKm1TV7gHDP2Fi5WHGjFqq3XYptgBTPCTSEfyLX6Cwq1PUw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-14.2.12.tgz", + "integrity": "sha512-u2MH9+NRwbbFDRNiPWPexed9CnCq9+pGHLuyACSP2uR6Ik68cE6cayeZbIeoEV5vWpda/XsLmJgPJysw7dAZLQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.12" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-14.2.12.tgz", + "integrity": "sha512-9Gkb9KFkaQPz8XaS8ZwwTioRZ4ywykdAWyceICEi78/Y9ConYrTX2SbFogzI2dPUZU8a04tMlbqTSmHjVbJftQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.17.2", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.26.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/main-ngcc.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/compiler": "14.2.12", + "typescript": ">=4.6.2 <4.9" + } + }, + "node_modules/@angular/core": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.2.12.tgz", + "integrity": "sha512-sGQxU5u4uawwvJa6jOTmGoisJiQ5HIN/RoBw99CmoqZIVyUSg9IRJJC1KVdH8gbpWBNLkElZv21lwJTL/msWyg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.11.4 || ~0.12.0" + } + }, + "node_modules/@angular/forms": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-14.2.12.tgz", + "integrity": "sha512-7abYlGIT2JnAtutQUlH3fQS6QEpbfftgvsVcZJCyvX0rXL3u2w2vUQkDHJH4YJJp3AHFVCH4/l7R4VcaPnrwvA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.2.12", + "@angular/core": "14.2.12", + "@angular/platform-browser": "14.2.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.12.tgz", + "integrity": "sha512-vOarWym8ucl1gjYWCzdwyBha+MTvL381mvTTUu8aUx6nVhHFjv4bvpjlZnZgojecqUPyxOwmPLLHvCZPJVHZYg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/animations": "14.2.12", + "@angular/common": "14.2.12", + "@angular/core": "14.2.12" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.2.12.tgz", + "integrity": "sha512-oZhNJeaBmgw8+KBSYpKz2RYqEDyETC+HJXH8dwIFcP6BqqwL2NE70FdSR7EnOa5c41MEtTmMCGhrJSFR60x5/w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.2.12", + "@angular/compiler": "14.2.12", + "@angular/core": "14.2.12", + "@angular/platform-browser": "14.2.12" + } + }, + "node_modules/@angular/router": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-14.2.12.tgz", + "integrity": "sha512-r5tVus5RJDNc4U2v0jMtjPiAS1xDsVsJ70lS313DgZmBDHIVZP1cWIehdxwgNlGwQQtAA36eG7toBwqUU3gb/A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.2.12", + "@angular/core": "14.2.12", + "@angular/platform-browser": "14.2.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz", + "integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.2.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", + "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "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.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", + "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", + "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", + "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", + "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", + "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "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.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", + "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "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-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", + "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-simple-access": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", + "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "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-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "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, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "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, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz", + "integrity": "sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@ngtools/webpack": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.10.tgz", + "integrity": "sha512-sLHapZLVub6mEz5b19tf1VfIV1w3tYfg7FNPLeni79aldxu1FbP1v2WmiFAnMzrswqyK0bhTtxrl+Z/CLKqyoQ==", + "dev": true, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0", + "typescript": ">=4.6.2 <4.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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz", + "integrity": "sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/git/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", + "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "dev": true, + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz", + "integrity": "sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/run-script/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.8" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^2.42.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@schematics/angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-14.2.10.tgz", + "integrity": "sha512-YFTc/9QJdx422XcApizEcVLKoyknu8b9zHIlAepZCu7WkV8GPT0hvVEHQ7KBWys5aQ7pPZMT0JpZLeAz0F2xYQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "jsonc-parser": "3.1.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.4.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", + "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", + "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.31", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.32", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz", + "integrity": "sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.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 + }, + "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 + }, + "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 + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "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, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "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, + "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, + "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": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/agentkeepalive/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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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, + "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, + "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, + "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, + "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" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-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, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.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 + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + } + ] + }, + "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, + "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 + }, + "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, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "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, + "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 + }, + "node_modules/bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "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 + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001442", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", + "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "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, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "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, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "color-name": "1.1.3" + } + }, + "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 + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "node_modules/commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "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 + }, + "node_modules/compression/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 + }, + "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 + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "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, + "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, + "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 + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/core-js-compat": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.1.tgz", + "integrity": "sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.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 + }, + "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, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + } + }, + "node_modules/critters/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/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, + "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/critters/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/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==", + "dev": true + }, + "node_modules/critters/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "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, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.2.0.tgz", + "integrity": "sha512-JYlIsE7eKHSi0UNuCyo96YuIDFqvhGgHw4Ck6lsN+DP0Tp8M64UTDT2trGbkMDqnCoEjks7CkS0XcjU0rkvBdg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "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, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true + }, + "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 + }, + "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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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 + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "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, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?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" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "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 + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "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 + }, + "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, + "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, + "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, + "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, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.5.tgz", + "integrity": "sha512-mjEyaa4zhuuRhaSLOdjEb57X0XPP9JEsnXI4E+ivhwT0GgzUogARx4MqoY1jQyB+4Bkz3BUOmzL7t9RMKmlG3g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "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, + "engines": { + "node": ">=6" + } + }, + "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 + }, + "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, + "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, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz", + "integrity": "sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.15.5", + "esbuild-android-64": "0.15.5", + "esbuild-android-arm64": "0.15.5", + "esbuild-darwin-64": "0.15.5", + "esbuild-darwin-arm64": "0.15.5", + "esbuild-freebsd-64": "0.15.5", + "esbuild-freebsd-arm64": "0.15.5", + "esbuild-linux-32": "0.15.5", + "esbuild-linux-64": "0.15.5", + "esbuild-linux-arm": "0.15.5", + "esbuild-linux-arm64": "0.15.5", + "esbuild-linux-mips64le": "0.15.5", + "esbuild-linux-ppc64le": "0.15.5", + "esbuild-linux-riscv64": "0.15.5", + "esbuild-linux-s390x": "0.15.5", + "esbuild-netbsd-64": "0.15.5", + "esbuild-openbsd-64": "0.15.5", + "esbuild-sunos-64": "0.15.5", + "esbuild-windows-32": "0.15.5", + "esbuild-windows-64": "0.15.5", + "esbuild-windows-arm64": "0.15.5" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz", + "integrity": "sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz", + "integrity": "sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz", + "integrity": "sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz", + "integrity": "sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz", + "integrity": "sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz", + "integrity": "sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz", + "integrity": "sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz", + "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz", + "integrity": "sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz", + "integrity": "sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz", + "integrity": "sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz", + "integrity": "sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz", + "integrity": "sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz", + "integrity": "sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz", + "integrity": "sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz", + "integrity": "sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz", + "integrity": "sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.5.tgz", + "integrity": "sha512-lTJOEKekN/4JI/eOEq0wLcx53co2N6vaT/XjBz46D1tvIVoUEyM0o2K6txW6gEotf31szFD/J1PbxmnbkGlK9A==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz", + "integrity": "sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz", + "integrity": "sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz", + "integrity": "sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "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 + }, + "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, + "engines": { + "node": ">=0.8.0" + } + }, + "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, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "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, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "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, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "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" + } + }, + "node_modules/express/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 + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "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, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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 + }, + "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, + "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 + }, + "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, + "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 + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "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.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "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 + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "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, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "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, + "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, + "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 + }, + "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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "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, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "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 + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "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==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "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/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "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 + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "dependencies": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "node_modules/hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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 + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "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, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "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/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "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, + "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, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ignore-walk/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", + "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "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==", + "dev": true, + "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 + }, + "node_modules/ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/injection-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.4.0.tgz", + "integrity": "sha512-6jiJt0tCAo9zjHbcwLiPL+IuNe9SQ6a9g0PEzafThW3fOQi0mrmiJGBJvDD6tmhPh8cQHIQtCOrJuBfQME4kPA==", + "dev": true, + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/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, + "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/inquirer/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/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==", + "dev": true + }, + "node_modules/inquirer/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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 + }, + "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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz", + "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "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, + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "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, + "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 + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jasmine-core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.3.0.tgz", + "integrity": "sha512-qybtBUesniQdW6n+QIHMng2vDOHscIC/dEXjW+JzO9+LoAZMb03RCUC5xFOv/btSKPm1xL42fn+RjlU4oB42Lg==", + "dev": true + }, + "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, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "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 + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "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" + ] + }, + "node_modules/karma": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", + "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "dev": true, + "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.4.1", + "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, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", + "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "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-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.0.0.tgz", + "integrity": "sha512-SB8HNNiazAHXM1vGEzf8/tSyEhkfxuDdhYdPBX2Mwgzt0OuF2gicApQ+uvXLID/gXyJQgvrM9+1/2SxZFUUDIA==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "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, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "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, + "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/karma/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, + "engines": { + "node": ">=10" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "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": "11.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz", + "integrity": "sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "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, + "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, + "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, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "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, + "optional": true, + "engines": { + "node": ">=0.10.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, + "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 + }, + "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, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "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 + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/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, + "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/log-symbols/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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==", + "dev": true + }, + "node_modules/log-symbols/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.1.tgz", + "integrity": "sha512-lzbd0Eq1HRdWM2abSD7mk6YIVY0AogGJzb/z+lqzRk+8+XJP+M6L1MS5FUSc3jjGru4dbKjEMJmqlsoYYpuivQ==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" + }, + "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==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz", + "integrity": "sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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, + "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, + "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, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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 + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/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 + }, + "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, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/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 + }, + "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, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "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, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "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, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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 + }, + "node_modules/ng-packagr": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-14.2.2.tgz", + "integrity": "sha512-AqwHcMM6x+JkCHT++IsbulnTdyoXcC2Cr4tbPamuieacc77+fFbB195hdcqEFwsKX5410cymx/ZUyHird9rxlg==", + "dev": true, + "dependencies": { + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.1.3", + "ajv": "^8.10.0", + "ansi-colors": "^4.1.1", + "browserslist": "^4.20.0", + "cacache": "^16.0.0", + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "dependency-graph": "^0.11.0", + "esbuild-wasm": "^0.15.0", + "find-cache-dir": "^3.3.2", + "glob": "^8.0.0", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.0.0", + "less": "^4.1.2", + "ora": "^5.1.0", + "postcss": "^8.4.8", + "postcss-preset-env": "^7.4.2", + "postcss-url": "^10.1.3", + "rollup": "^2.70.0", + "rollup-plugin-sourcemaps": "^0.6.3", + "rxjs": "^7.5.5", + "sass": "^1.49.9", + "stylus": "^0.59.0" + }, + "bin": { + "ng-packagr": "cli/main.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "optionalDependencies": { + "esbuild": "^0.15.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0 || ^14.0.0-next || ^14.2.0-next", + "tslib": "^2.3.0", + "typescript": ">=4.6.2 <4.9" + } + }, + "node_modules/ng-packagr/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ng-packagr/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ng-packagr/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "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, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-releases": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "dev": true + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz", + "integrity": "sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-install-checks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", + "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "node_modules/npm-package-arg": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.0.tgz", + "integrity": "sha512-4J0GL+u2Nh6OnhvUKXRr2ZMG4lR8qtLp+kv7UiV00Y+nGiSxtttCyIRHCt5L5BNkXQld/RceYItau3MDOoGiBw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm-packlist/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm-packlist/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-packlist/node_modules/npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz", + "integrity": "sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz", + "integrity": "sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "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, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/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, + "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/ora/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/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==", + "dev": true + }, + "node_modules/ora/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", + "integrity": "sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg==", + "dev": true, + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/pacote/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "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, + "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-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, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.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, + "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, + "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, + "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, + "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 + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "dependencies": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0" + }, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.0.0.tgz", + "integrity": "sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "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, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.0.tgz", + "integrity": "sha512-leqiqLOellpLKfbHkD06E04P6d9ZQ24mat6hu4NSqun7WG0UhspHR5Myiv/510qouCjoo4+YJtNOqg5xHaFnCA==", + "dev": true, + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.0.5", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.8", + "browserslist": "^4.21.3", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.0.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.8", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.10", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-url": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", + "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", + "dev": true, + "dependencies": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "~3.0.4", + "xxhashjs": "~0.2.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-url/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-url/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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 + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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 + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "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, + "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, + "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, + "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, + "optional": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "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" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "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-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-package-json": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz", + "integrity": "sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-package-json/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/read-package-json/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-package-json/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "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 + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "node_modules/regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "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==", + "dev": true, + "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, + "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 + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-sourcemaps": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz", + "integrity": "sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.9", + "source-map-resolve": "^0.6.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "@types/node": ">=10.0.0", + "rollup": ">=0.31.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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" + } + ] + }, + "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 + }, + "node_modules/sass": { + "version": "1.54.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.4.tgz", + "integrity": "sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.0.2.tgz", + "integrity": "sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/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, + "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/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/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 + }, + "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 + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/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 + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "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, + "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 + }, + "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, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/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==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "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, + "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, + "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, + "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, + "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 + }, + "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 + }, + "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 + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "node_modules/socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.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, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "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, + "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, + "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.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.4.tgz", + "integrity": "sha512-Ha1Ccw2/N5C/IF8Do6zgNe8F3jQo8MPBnMBGvX0QjNv/I97BcNRzK6/mzOpZHHK7DjMLTI3c7Xw7Y1KvdChkvw==", + "dev": true, + "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, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/stylus-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.0.0.tgz", + "integrity": "sha512-WTbtLrNfOfLgzTaR9Lj/BPhQroKk/LC1hfTXSUbrxmxgfUo3Y3LpmKRVA2R1XbjvTAvOfaian9vOyfv1z99E+A==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "klona": "^2.0.5", + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "stylus": ">=0.52.4", + "webpack": "^5.0.0" + } + }, + "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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "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 + }, + "node_modules/terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "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.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.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/terser-webpack-plugin/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, + "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/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/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 + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/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 + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "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 + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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, + "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, + "engines": { + "node": ">=0.6" + } + }, + "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, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "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, + "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, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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 + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "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, + "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.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "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 + }, + "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, + "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, + "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, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "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, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "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-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.0.tgz", + "integrity": "sha512-L5S4Q2zT57SK7tazgzjMiSMBdsw+rGYIX27MgPgx7LDhWO0lViPrHKoLS7jo5In06PWYAhlYu3PbyoC6yAThbw==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "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": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "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, + "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/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/webpack/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, + "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/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/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 + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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, + "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, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "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, + "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/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true + }, + "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 + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "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/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "dependencies": { + "cuint": "^0.2.2" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "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.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/zone.js": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz", + "integrity": "sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==", + "dependencies": { + "tslib": "^2.3.0" + } + } + }, + "dependencies": { + "@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@angular-devkit/architect": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.10.tgz", + "integrity": "sha512-/6YmPrgataj1jD2Uqd1ED+CG4DaZGacoeZd/89hH7hF76Nno8K18DrSOqJAEmDnOWegpSRGVLd0qP09IHmaG5w==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.2.10", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/build-angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.10.tgz", + "integrity": "sha512-VCeZAyq4uPCJukKInaSiD4i/GgxgcU4jFlLFQtoYNmaBS4xbPOymL19forRIihiV0dwNEa2L694vRTAPMBxIfw==", + "dev": true, + "requires": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/build-webpack": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@babel/core": "7.18.10", + "@babel/generator": "7.18.12", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.18.10", + "@babel/plugin-transform-async-to-generator": "7.18.6", + "@babel/plugin-transform-runtime": "7.18.10", + "@babel/preset-env": "7.18.10", + "@babel/runtime": "7.18.9", + "@babel/template": "7.18.10", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "14.2.10", + "ansi-colors": "4.1.3", + "babel-loader": "8.2.5", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.9.1", + "cacache": "16.1.2", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.1", + "esbuild": "0.15.5", + "esbuild-wasm": "0.15.5", + "glob": "8.0.3", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.0.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "mini-css-extract-plugin": "2.6.1", + "minimatch": "5.1.0", + "open": "8.4.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "6.0.1", + "piscina": "3.2.0", + "postcss": "8.4.16", + "postcss-import": "15.0.0", + "postcss-loader": "7.0.1", + "postcss-preset-env": "7.8.0", + "regenerator-runtime": "0.13.9", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.54.4", + "sass-loader": "13.0.2", + "semver": "7.3.7", + "source-map-loader": "4.0.0", + "source-map-support": "0.5.21", + "stylus": "0.59.0", + "stylus-loader": "7.0.0", + "terser": "5.14.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.4.0", + "webpack": "5.74.0", + "webpack-dev-middleware": "5.3.3", + "webpack-dev-server": "4.11.0", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", + "integrity": "sha512-Xx+xPlfCZIUHagysjjOAje9nRo8pRDczQCcXb4J2O0BLtH+xeVue6ba4y1kfJfQMAnM2mkcoMIAyOctlaRGWYA==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.10.tgz", + "integrity": "sha512-h+2MaSY7QSvoJ3R+Hvin21jVCfPGOTLdASIUk4Jmq6J3y5BSku3KSSaV8dWoBOBkFCwQyPQMRjiHoHKLpC1K7g==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1402.10", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/core": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.10.tgz", + "integrity": "sha512-K4AO7mROTdbhQ7chtyQd6oPwmuL+BPUh+wn6Aq1qrmYJK4UZYFOPp8fi/Ehs8meCEeywtrssOPfrOE4Gsre9dg==", + "dev": true, + "requires": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/schematics": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.10.tgz", + "integrity": "sha512-MMp31KpJTwKHisXOq+6VOXYApq97hZxFaFmZk396X5aIFTCELUwjcezQDk+u2nEs5iK/COUfnN3plGcfJxYhQA==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.2.10", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular/animations": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-14.2.12.tgz", + "integrity": "sha512-gwdnFZkvVUr+enUNfhfCGRGGqNHn1+vTA81apLfHYhJxgjiLUtETc4KTOrQevtDm022pEd+LSrvr8r+7ag+jkw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/cli": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.10.tgz", + "integrity": "sha512-gX9sAKOwq4lKdPWeABB7TzKDHdjQXvkUU8NmPJA6mEAVXvm3lhQtFvHDalZstwK8au2LY0LaXTcEtcKYOt3AXQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "@schematics/angular": "14.2.10", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "debug": "4.3.4", + "ini": "3.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "npm-package-arg": "9.1.0", + "npm-pick-manifest": "7.0.1", + "open": "8.4.0", + "ora": "5.4.1", + "pacote": "13.6.2", + "resolve": "1.22.1", + "semver": "7.3.7", + "symbol-observable": "4.0.0", + "uuid": "8.3.2", + "yargs": "17.5.1" + } + }, + "@angular/common": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.2.12.tgz", + "integrity": "sha512-oZunh9wfInFWhNO1P8uoEs/o4u8kerKMhw8GruywKm1TV7gHDP2Fi5WHGjFqq3XYptgBTPCTSEfyLX6Cwq1PUw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-14.2.12.tgz", + "integrity": "sha512-u2MH9+NRwbbFDRNiPWPexed9CnCq9+pGHLuyACSP2uR6Ik68cE6cayeZbIeoEV5vWpda/XsLmJgPJysw7dAZLQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler-cli": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-14.2.12.tgz", + "integrity": "sha512-9Gkb9KFkaQPz8XaS8ZwwTioRZ4ywykdAWyceICEi78/Y9ConYrTX2SbFogzI2dPUZU8a04tMlbqTSmHjVbJftQ==", + "dev": true, + "requires": { + "@babel/core": "^7.17.2", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.26.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + } + }, + "@angular/core": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.2.12.tgz", + "integrity": "sha512-sGQxU5u4uawwvJa6jOTmGoisJiQ5HIN/RoBw99CmoqZIVyUSg9IRJJC1KVdH8gbpWBNLkElZv21lwJTL/msWyg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/forms": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-14.2.12.tgz", + "integrity": "sha512-7abYlGIT2JnAtutQUlH3fQS6QEpbfftgvsVcZJCyvX0rXL3u2w2vUQkDHJH4YJJp3AHFVCH4/l7R4VcaPnrwvA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.12.tgz", + "integrity": "sha512-vOarWym8ucl1gjYWCzdwyBha+MTvL381mvTTUu8aUx6nVhHFjv4bvpjlZnZgojecqUPyxOwmPLLHvCZPJVHZYg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.2.12.tgz", + "integrity": "sha512-oZhNJeaBmgw8+KBSYpKz2RYqEDyETC+HJXH8dwIFcP6BqqwL2NE70FdSR7EnOa5c41MEtTmMCGhrJSFR60x5/w==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/router": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-14.2.12.tgz", + "integrity": "sha512-r5tVus5RJDNc4U2v0jMtjPiAS1xDsVsJ70lS313DgZmBDHIVZP1cWIehdxwgNlGwQQtAA36eG7toBwqUU3gb/A==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.20.10", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", + "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "dev": true + }, + "@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz", + "integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.2.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", + "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + } + }, + "@babel/helpers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "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.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", + "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", + "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", + "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", + "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", + "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", + "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", + "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-simple-access": "^7.20.2" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", + "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@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 + }, + "@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + } + }, + "@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "requires": {} + }, + "@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "requires": {} + }, + "@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 + }, + "@esbuild/linux-loong64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz", + "integrity": "sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A==", + "dev": true, + "optional": true + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@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 + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@ngtools/webpack": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.10.tgz", + "integrity": "sha512-sLHapZLVub6mEz5b19tf1VfIV1w3tYfg7FNPLeni79aldxu1FbP1v2WmiFAnMzrswqyK0bhTtxrl+Z/CLKqyoQ==", + "dev": true, + "requires": {} + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz", + "integrity": "sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "dependencies": { + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "@npmcli/node-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", + "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "dev": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz", + "integrity": "sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@rollup/plugin-json": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.8" + } + }, + "@rollup/plugin-node-resolve": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@schematics/angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-14.2.10.tgz", + "integrity": "sha512-YFTc/9QJdx422XcApizEcVLKoyknu8b9zHIlAepZCu7WkV8GPT0hvVEHQ7KBWys5aQ7pPZMT0JpZLeAz0F2xYQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "jsonc-parser": "3.1.0" + } + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/eslint": { + "version": "8.4.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", + "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/express": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", + "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.31", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.32", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz", + "integrity": "sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "@types/node": { + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@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 + }, + "@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 + }, + "@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 + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "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, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "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, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + } + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "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, + "requires": { + "ajv": "^8.0.0" + } + }, + "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, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "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 + }, + "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, + "requires": { + "type-fest": "^0.21.3" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "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, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "requires": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + }, + "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 + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "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 + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001442", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", + "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "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, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "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, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "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, + "requires": { + "color-name": "1.1.3" + } + }, + "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 + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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 + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "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 + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "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==", + "dev": true + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "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, + "requires": { + "is-what": "^3.14.1" + } + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "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, + "requires": { + "is-glob": "^4.0.3" + } + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "core-js-compat": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.1.tgz", + "integrity": "sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==", + "dev": true, + "requires": { + "browserslist": "^4.21.4" + } + }, + "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 + }, + "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, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + } + }, + "css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "requires": {} + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "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 + }, + "cssdb": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.2.0.tgz", + "integrity": "sha512-JYlIsE7eKHSi0UNuCyo96YuIDFqvhGgHw4Ck6lsN+DP0Tp8M64UTDT2trGbkMDqnCoEjks7CkS0XcjU0rkvBdg==", + "dev": true + }, + "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 + }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true + }, + "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 + }, + "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 + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "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 + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "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 + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "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, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "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 + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "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 + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "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 + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "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, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "engine.io": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + } + }, + "engine.io-parser": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.5.tgz", + "integrity": "sha512-mjEyaa4zhuuRhaSLOdjEb57X0XPP9JEsnXI4E+ivhwT0GgzUogARx4MqoY1jQyB+4Bkz3BUOmzL7t9RMKmlG3g==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "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, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "esbuild": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz", + "integrity": "sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg==", + "dev": true, + "optional": true, + "requires": { + "@esbuild/linux-loong64": "0.15.5", + "esbuild-android-64": "0.15.5", + "esbuild-android-arm64": "0.15.5", + "esbuild-darwin-64": "0.15.5", + "esbuild-darwin-arm64": "0.15.5", + "esbuild-freebsd-64": "0.15.5", + "esbuild-freebsd-arm64": "0.15.5", + "esbuild-linux-32": "0.15.5", + "esbuild-linux-64": "0.15.5", + "esbuild-linux-arm": "0.15.5", + "esbuild-linux-arm64": "0.15.5", + "esbuild-linux-mips64le": "0.15.5", + "esbuild-linux-ppc64le": "0.15.5", + "esbuild-linux-riscv64": "0.15.5", + "esbuild-linux-s390x": "0.15.5", + "esbuild-netbsd-64": "0.15.5", + "esbuild-openbsd-64": "0.15.5", + "esbuild-sunos-64": "0.15.5", + "esbuild-windows-32": "0.15.5", + "esbuild-windows-64": "0.15.5", + "esbuild-windows-arm64": "0.15.5" + } + }, + "esbuild-android-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz", + "integrity": "sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz", + "integrity": "sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz", + "integrity": "sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz", + "integrity": "sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz", + "integrity": "sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz", + "integrity": "sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz", + "integrity": "sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz", + "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz", + "integrity": "sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz", + "integrity": "sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz", + "integrity": "sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz", + "integrity": "sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz", + "integrity": "sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz", + "integrity": "sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz", + "integrity": "sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz", + "integrity": "sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz", + "integrity": "sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA==", + "dev": true, + "optional": true + }, + "esbuild-wasm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.5.tgz", + "integrity": "sha512-lTJOEKekN/4JI/eOEq0wLcx53co2N6vaT/XjBz46D1tvIVoUEyM0o2K6txW6gEotf31szFD/J1PbxmnbkGlK9A==", + "dev": true + }, + "esbuild-windows-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz", + "integrity": "sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz", + "integrity": "sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz", + "integrity": "sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA==", + "dev": true, + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "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, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "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 + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "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, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "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 + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "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 + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "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, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "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 + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "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 + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "requires": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + } + } + }, + "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, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "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, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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 + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "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, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@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" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "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, + "requires": {} + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "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, + "optional": true + }, + "immutable": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.2.tgz", + "integrity": "sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true + }, + "injection-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.4.0.tgz", + "integrity": "sha512-6jiJt0tCAo9zjHbcwLiPL+IuNe9SQ6a9g0PEzafThW3fOQi0mrmiJGBJvDD6tmhPh8cQHIQtCOrJuBfQME4kPA==", + "dev": true, + "requires": { + "tslib": "^2.0.0" + } + }, + "inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-builtin-module": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz", + "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "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 + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jasmine-core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.3.0.tgz", + "integrity": "sha512-qybtBUesniQdW6n+QIHMng2vDOHscIC/dEXjW+JzO9+LoAZMb03RCUC5xFOv/btSKPm1xL42fn+RjlU4oB42Lg==", + "dev": true + }, + "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, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "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 + }, + "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 + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "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 + }, + "karma": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", + "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "dev": true, + "requires": { + "@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.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "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 + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "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" + } + }, + "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 + } + } + }, + "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, + "requires": { + "which": "^1.2.1" + } + }, + "karma-coverage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", + "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "requires": { + "jasmine-core": "^4.1.0" + } + }, + "karma-jasmine-html-reporter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.0.0.tgz", + "integrity": "sha512-SB8HNNiazAHXM1vGEzf8/tSyEhkfxuDdhYdPBX2Mwgzt0OuF2gicApQ+uvXLID/gXyJQgvrM9+1/2SxZFUUDIA==", + "dev": true, + "requires": {} + }, + "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, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "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 + }, + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "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", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "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, + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + }, + "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, + "optional": true + } + } + }, + "less-loader": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz", + "integrity": "sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==", + "dev": true, + "requires": { + "klona": "^2.0.4" + } + }, + "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, + "requires": { + "webpack-sources": "^3.0.0" + } + }, + "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 + }, + "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 + }, + "loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "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 + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log4js": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.1.tgz", + "integrity": "sha512-lzbd0Eq1HRdWM2abSD7mk6YIVY0AogGJzb/z+lqzRk+8+XJP+M6L1MS5FUSc3jjGru4dbKjEMJmqlsoYYpuivQ==", + "dev": true, + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" + } + }, + "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==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "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, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + } + } + }, + "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 + }, + "memfs": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz", + "integrity": "sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "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 + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "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, + "requires": { + "minimist": "^1.2.6" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "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, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "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 + }, + "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 + }, + "ng-packagr": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-14.2.2.tgz", + "integrity": "sha512-AqwHcMM6x+JkCHT++IsbulnTdyoXcC2Cr4tbPamuieacc77+fFbB195hdcqEFwsKX5410cymx/ZUyHird9rxlg==", + "dev": true, + "requires": { + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.1.3", + "ajv": "^8.10.0", + "ansi-colors": "^4.1.1", + "browserslist": "^4.20.0", + "cacache": "^16.0.0", + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "dependency-graph": "^0.11.0", + "esbuild": "^0.15.0", + "esbuild-wasm": "^0.15.0", + "find-cache-dir": "^3.3.2", + "glob": "^8.0.0", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.0.0", + "less": "^4.1.2", + "ora": "^5.1.0", + "postcss": "^8.4.8", + "postcss-preset-env": "^7.4.2", + "postcss-url": "^10.1.3", + "rollup": "^2.70.0", + "rollup-plugin-sourcemaps": "^0.6.3", + "rxjs": "^7.5.5", + "sass": "^1.49.9", + "stylus": "^0.59.0" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "optional": true, + "requires": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "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 + }, + "node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "dev": true, + "optional": true + }, + "node-releases": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", + "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==", + "dev": true + }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "normalize-package-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz", + "integrity": "sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg==", + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "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 + }, + "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 + }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", + "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.0.tgz", + "integrity": "sha512-4J0GL+u2Nh6OnhvUKXRr2ZMG4lR8qtLp+kv7UiV00Y+nGiSxtttCyIRHCt5L5BNkXQld/RceYItau3MDOoGiBw==", + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + } + }, + "npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^2.0.0" + } + }, + "npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true + } + } + }, + "npm-pick-manifest": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz", + "integrity": "sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg==", + "dev": true, + "requires": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz", + "integrity": "sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw==", + "dev": true, + "requires": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "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, + "requires": { + "boolbase": "^1.0.0" + } + }, + "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 + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "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, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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 + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "dependencies": { + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", + "integrity": "sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg==", + "dev": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "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 + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "requires": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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 + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "requires": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0", + "nice-napi": "^1.0.2" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "requires": {} + }, + "postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "requires": {} + }, + "postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-import": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.0.0.tgz", + "integrity": "sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "requires": {} + }, + "postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + } + }, + "postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "requires": {} + }, + "postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "requires": {} + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "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, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "dev": true, + "requires": {} + }, + "postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "requires": {} + }, + "postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-preset-env": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.0.tgz", + "integrity": "sha512-leqiqLOellpLKfbHkD06E04P6d9ZQ24mat6hu4NSqun7WG0UhspHR5Myiv/510qouCjoo4+YJtNOqg5xHaFnCA==", + "dev": true, + "requires": { + "@csstools/postcss-cascade-layers": "^1.0.5", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.8", + "browserslist": "^4.21.3", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.0.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.8", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.10", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "requires": {} + }, + "postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-selector-parser": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", + "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-url": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", + "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", + "dev": true, + "requires": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "~3.0.4", + "xxhashjs": "~0.2.2" + }, + "dependencies": { + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + }, + "minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "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 + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true + }, + "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 + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "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, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "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, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "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 + } + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "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 + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "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 + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "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 + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "read-package-json": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz", + "integrity": "sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true + } + } + }, + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "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 + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "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==", + "dev": true + }, + "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 + }, + "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 + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "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 + } + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-sourcemaps": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz", + "integrity": "sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.0.9", + "source-map-resolve": "^0.6.0" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "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 + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass": { + "version": "1.54.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.4.tgz", + "integrity": "sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.0.2.tgz", + "integrity": "sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + } + } + }, + "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 + }, + "selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "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, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "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, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "requires": { + "kind-of": "^6.0.2" + } + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "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 + }, + "socket.io": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", + "integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.1", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.1" + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dev": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "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, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "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 + }, + "streamroller": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.4.tgz", + "integrity": "sha512-Ha1Ccw2/N5C/IF8Do6zgNe8F3jQo8MPBnMBGvX0QjNv/I97BcNRzK6/mzOpZHHK7DjMLTI3c7Xw7Y1KvdChkvw==", + "dev": true, + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + } + }, + "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, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + } + }, + "stylus-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.0.0.tgz", + "integrity": "sha512-WTbtLrNfOfLgzTaR9Lj/BPhQroKk/LC1hfTXSUbrxmxgfUo3Y3LpmKRVA2R1XbjvTAvOfaian9vOyfv1z99E+A==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "klona": "^2.0.5", + "normalize-path": "^3.0.0" + } + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + }, + "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 + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz", + "integrity": "sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "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 + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "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 + }, + "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 + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "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 + }, + "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, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "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 + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "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, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "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 + }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "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 + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "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 + }, + "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 + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "webpack-dev-server": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.0.tgz", + "integrity": "sha512-L5S4Q2zT57SK7tazgzjMiSMBdsw+rGYIX27MgPgx7LDhWO0lViPrHKoLS7jo5In06PWYAhlYu3PbyoC6yAThbw==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "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, + "requires": { + "typed-assert": "^1.0.8" + } + }, + "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, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "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 + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + } + } + }, + "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 + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "requires": {} + }, + "xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "requires": { + "cuint": "^0.2.2" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "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.0.0" + } + }, + "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==", + "dev": true + }, + "zone.js": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz", + "integrity": "sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==", + "requires": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000000..309ce19956 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,41 @@ +{ + "name": "frontend", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build common && ng build public && ng build admin", + "watch": "ng build --watch --configuration development", + "test": "ng test", + "postinstall": "(cd projects/common && npm install) && (cd projects/public && npm install) && (cd projects/admin && npm install)", + "prebuild": "npm --prefix projects/admin run svg" + }, + "private": true, + "dependencies": { + "@angular/animations": "^14.2.0", + "@angular/common": "^14.2.0", + "@angular/compiler": "^14.2.0", + "@angular/core": "^14.2.0", + "@angular/forms": "^14.2.0", + "@angular/platform-browser": "^14.2.0", + "@angular/platform-browser-dynamic": "^14.2.0", + "@angular/router": "^14.2.0", + "rxjs": "~7.5.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.2.10", + "@angular/cli": "~14.2.10", + "@angular/compiler-cli": "^14.2.0", + "@types/jasmine": "~4.0.0", + "jasmine-core": "~4.3.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.0.0", + "ng-packagr": "^14.2.0", + "typescript": "~4.7.2" + } +} diff --git a/frontend/projects/admin/README.md b/frontend/projects/admin/README.md new file mode 100644 index 0000000000..a32b4efa1b --- /dev/null +++ b/frontend/projects/admin/README.md @@ -0,0 +1,38 @@ +# AlfioAdmin + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.10. + +## Icons + +1. Fetch svg icon here: https://fonts.google.com/icons?icon.style=Outlined +2. Put them in `src/assets/svg` +3. Run `npm run svg` to generate icon code +4. Import them in `shared/icons.ts` +5. Reference them with `<svg-icon key="key"></svg-icon>` + +Note you may need to import the icon component + the list of icons if you are in a new module, see `app.module.ts` or +`dashboard.module.ts`. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/frontend/projects/admin/package-lock.json b/frontend/projects/admin/package-lock.json new file mode 100644 index 0000000000..fcf1ac95ff --- /dev/null +++ b/frontend/projects/admin/package-lock.json @@ -0,0 +1,26969 @@ +{ + "name": "alfio-admin", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "alfio-admin", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^14.2.0", + "@angular/cdk": "^14.2.7", + "@angular/common": "^14.2.0", + "@angular/compiler": "^14.2.0", + "@angular/core": "^14.2.0", + "@angular/forms": "^14.2.0", + "@angular/platform-browser": "^14.2.0", + "@angular/platform-browser-dynamic": "^14.2.0", + "@angular/pwa": "^0.5.3", + "@angular/router": "^14.2.0", + "@ng-bootstrap/ng-bootstrap": "^13.1.1", + "@ngneat/svg-icon": "^6.1.0", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "@popperjs/core": "^2.10.2", + "bootstrap": "^5.2.0", + "ng2-charts": "^4.1.1", + "rxjs": "~7.5.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.2.10", + "@angular-eslint/builder": "14.4.0", + "@angular-eslint/eslint-plugin": "14.4.0", + "@angular-eslint/eslint-plugin-template": "14.4.0", + "@angular-eslint/schematics": "14.4.0", + "@angular-eslint/template-parser": "14.4.0", + "@angular/cli": "~14.2.10", + "@angular/compiler-cli": "^14.2.0", + "@angular/localize": "^14.2.0", + "@ngneat/svg-generator": "^6.0.0", + "@types/jasmine": "~4.0.0", + "@typescript-eslint/eslint-plugin": "5.43.0", + "@typescript-eslint/parser": "5.43.0", + "eslint": "^8.28.0", + "jasmine-core": "~4.3.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.0.0", + "typescript": "~4.7.2" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.10.tgz", + "integrity": "sha512-/6YmPrgataj1jD2Uqd1ED+CG4DaZGacoeZd/89hH7hF76Nno8K18DrSOqJAEmDnOWegpSRGVLd0qP09IHmaG5w==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.2.10", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.10.tgz", + "integrity": "sha512-VCeZAyq4uPCJukKInaSiD4i/GgxgcU4jFlLFQtoYNmaBS4xbPOymL19forRIihiV0dwNEa2L694vRTAPMBxIfw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/build-webpack": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@babel/core": "7.18.10", + "@babel/generator": "7.18.12", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.18.10", + "@babel/plugin-transform-async-to-generator": "7.18.6", + "@babel/plugin-transform-runtime": "7.18.10", + "@babel/preset-env": "7.18.10", + "@babel/runtime": "7.18.9", + "@babel/template": "7.18.10", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "14.2.10", + "ansi-colors": "4.1.3", + "babel-loader": "8.2.5", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.9.1", + "cacache": "16.1.2", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.1", + "esbuild-wasm": "0.15.5", + "glob": "8.0.3", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.0.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "mini-css-extract-plugin": "2.6.1", + "minimatch": "5.1.0", + "open": "8.4.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "6.0.1", + "piscina": "3.2.0", + "postcss": "8.4.16", + "postcss-import": "15.0.0", + "postcss-loader": "7.0.1", + "postcss-preset-env": "7.8.0", + "regenerator-runtime": "0.13.9", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.54.4", + "sass-loader": "13.0.2", + "semver": "7.3.7", + "source-map-loader": "4.0.0", + "source-map-support": "0.5.21", + "stylus": "0.59.0", + "stylus-loader": "7.0.0", + "terser": "5.14.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.4.0", + "webpack": "5.74.0", + "webpack-dev-middleware": "5.3.3", + "webpack-dev-server": "4.11.0", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.15.5" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0", + "@angular/localize": "^14.0.0", + "@angular/service-worker": "^14.0.0", + "karma": "^6.3.0", + "ng-packagr": "^14.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=4.6.2 <4.9" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.10.tgz", + "integrity": "sha512-h+2MaSY7QSvoJ3R+Hvin21jVCfPGOTLdASIUk4Jmq6J3y5BSku3KSSaV8dWoBOBkFCwQyPQMRjiHoHKLpC1K7g==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1402.10", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/core": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.10.tgz", + "integrity": "sha512-K4AO7mROTdbhQ7chtyQd6oPwmuL+BPUh+wn6Aq1qrmYJK4UZYFOPp8fi/Ehs8meCEeywtrssOPfrOE4Gsre9dg==", + "dev": true, + "dependencies": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/schematics": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.10.tgz", + "integrity": "sha512-MMp31KpJTwKHisXOq+6VOXYApq97hZxFaFmZk396X5aIFTCELUwjcezQDk+u2nEs5iK/COUfnN3plGcfJxYhQA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.2.10", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-eslint/builder": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-14.4.0.tgz", + "integrity": "sha512-AhAUFvSg0urtb6Lsowvuxwu6DMXUy0BPwrnfNOBGjRt9vG7F9kgXXAsm5DnIS0GNy/mLZ9mSfa86fv++1e0KUA==", + "dev": true, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-14.4.0.tgz", + "integrity": "sha512-KMHPHd24s0HVvAP/DxSSqhYBWhwW8FgS/r0Uwv8eWpsIdc/z/Chd2ush2SgPchmmquAXTgOZsbEY7ZmW+XkJfQ==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-14.4.0.tgz", + "integrity": "sha512-2rZQ4mt7tEUW+lI5jjuj3HWaT4VQtWTG6+LDnmuUmx76m8hqQ7NvFUpOcNDofu5KbEVBP+oF2DA6wjoZOIuSOA==", + "dev": true, + "dependencies": { + "@angular-eslint/utils": "14.4.0", + "@typescript-eslint/utils": "5.43.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-14.4.0.tgz", + "integrity": "sha512-d3GM/EU2iWzr+BrITwO4gBf9WfDfuOdTjfinV/zN84oXMFaK2ENo+IP6OEsD9hh36rdPps+m2gFGDdx+rTzBpg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "14.4.0", + "@angular-eslint/utils": "14.4.0", + "@typescript-eslint/type-utils": "5.43.0", + "@typescript-eslint/utils": "5.43.0", + "aria-query": "5.1.3", + "axobject-query": "3.1.1" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-14.4.0.tgz", + "integrity": "sha512-BrGkPug+CZQWOfmNRsJDrEtYJcxvzF/kLlV7RjvIN9Ky5TjUiJVCeafl3VY6COSY32tjlh2GvBdl1AQKWWovbA==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "14.4.0", + "@angular-eslint/eslint-plugin-template": "14.4.0", + "ignore": "5.2.0", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "peerDependencies": { + "@angular/cli": ">= 14.0.0 < 15.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-14.4.0.tgz", + "integrity": "sha512-zq888KpQB0YTEK26mkKcT4fs8LDWWT1oAEXU8DrXhvkikS8XavTSHOWJye/bVZR4oJRFCF5YTJV75DEMcGNIpQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "14.4.0", + "eslint-scope": "^7.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@angular-eslint/template-parser/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-14.4.0.tgz", + "integrity": "sha512-dPHklAVfh+JfueDfXre9Xooq7p5bFyKO2Z6y1agYeofAgHCPIJOPx2AhtFPrOtsc4VXFFiyE9XbowlXh4ogoKQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "14.4.0", + "@typescript-eslint/utils": "5.43.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/animations": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-14.2.11.tgz", + "integrity": "sha512-HOw8xecbKfs7A5Ezjf+BfXKvvwU7X8I0US5Ey6bOuLvpA3QVOGSLw9BeutY5Q2mPWiRgnNNQW+FOd8Pe9gEkpQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.11" + } + }, + "node_modules/@angular/cdk": { + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.7.tgz", + "integrity": "sha512-/tEsYaUbDSnfEmKVvAMramIptmhI67O+9STjOV0i+74XR2NospeK0fkbywIANu1n3w6AHGMotvRWJrjmbCElFg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^5.0.0" + }, + "peerDependencies": { + "@angular/common": "^14.0.0 || ^15.0.0", + "@angular/core": "^14.0.0 || ^15.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + }, + "node_modules/@angular/cli": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.10.tgz", + "integrity": "sha512-gX9sAKOwq4lKdPWeABB7TzKDHdjQXvkUU8NmPJA6mEAVXvm3lhQtFvHDalZstwK8au2LY0LaXTcEtcKYOt3AXQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "@schematics/angular": "14.2.10", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "debug": "4.3.4", + "ini": "3.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "npm-package-arg": "9.1.0", + "npm-pick-manifest": "7.0.1", + "open": "8.4.0", + "ora": "5.4.1", + "pacote": "13.6.2", + "resolve": "1.22.1", + "semver": "7.3.7", + "symbol-observable": "4.0.0", + "uuid": "8.3.2", + "yargs": "17.5.1" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.2.11.tgz", + "integrity": "sha512-a5w7lz4SoUzCwSDnuUPnfbEYPA8ufFiXz44mOv48P4uIb78q3rZ/R/EyWD1O3yJPof0o8aPNKpKZzuRDv3Q8ow==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.11", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-14.2.11.tgz", + "integrity": "sha512-QD4tq37qqPxxNK4o0Pd7dJm06evwEPChV67S/ecX3S6UkSDp8lVoWKiVx9htp/5s4iydKZU4eGu9oTOMOLVdOw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.11" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-14.2.11.tgz", + "integrity": "sha512-ipIEgueW8bhxVSq6qlgndBLVRCJoTvk1he/TI3w34m2EnZY1ctgGGCm1VbB3XARh+irVesPVMIAxRtjYds7XOw==", + "dependencies": { + "@babel/core": "^7.17.2", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.26.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/main-ngcc.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/compiler": "14.2.11", + "typescript": ">=4.6.2 <4.9" + } + }, + "node_modules/@angular/core": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.2.11.tgz", + "integrity": "sha512-4uEIA6ESMLt2f/ivKuVBpME0IbuFHWmpweN4dwJt83DfJBiBfpqdrFYZHz/Kbkh9cGCiP7L4/eKPRWTlAHehhw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.11.4 || ~0.12.0" + } + }, + "node_modules/@angular/forms": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-14.2.11.tgz", + "integrity": "sha512-sItoA3/I8j/pf3zhv8sR37M5dAYUJpezv8rw2fTT2Y+nZJFUpkFWqX2N4qpMlPY0MP9OX++8K8/d2j0Lfi3wJQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.2.11", + "@angular/core": "14.2.11", + "@angular/platform-browser": "14.2.11", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/localize": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-14.2.11.tgz", + "integrity": "sha512-vrYv2f6BR1dKebZB+c0O4Arx7iGF1MqN4gHB+V5KHS7UPIc3YnoQn9LaFDRWWazjrXCvq1uf1Cb8qzzu19+lZQ==", + "dependencies": { + "@babel/core": "7.18.9", + "glob": "8.0.3", + "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": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/compiler": "14.2.11", + "@angular/compiler-cli": "14.2.11" + } + }, + "node_modules/@angular/localize/node_modules/@babel/core": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.9.tgz", + "integrity": "sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.9", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/localize/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/platform-browser": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.11.tgz", + "integrity": "sha512-lGi9pF0Kf/GGrVKcfxxfStM2eMSluDTmbcYuVAX28iBn5XEdfsonrkfy2cnxUMnQ7nioMAZBNGOJHbQPKz4jwg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/animations": "14.2.11", + "@angular/common": "14.2.11", + "@angular/core": "14.2.11" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.2.11.tgz", + "integrity": "sha512-kjcZda+gcAiYd0I3mjLSr6xR/HkUCnmIMyqaFGoHnIDXI2c6wLDxi49pivrJFvUYJPfYAJ6GjlYTM6L9B3XSEQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.2.11", + "@angular/compiler": "14.2.11", + "@angular/core": "14.2.11", + "@angular/platform-browser": "14.2.11" + } + }, + "node_modules/@angular/pwa": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-0.5.3.tgz", + "integrity": "sha512-htgdGgWHaAHRdGZ1qxkn/4bQZjx2np+A/oGtuN86z+/jWuY4S+lYfPjLQTmq5kZaZDQm+qm0TVvayCQFEAnagg==", + "dependencies": { + "@angular-devkit/core": "0.5.3", + "@angular-devkit/schematics": "0.5.3", + "@schematics/angular": "0.5.3", + "typescript": "~2.6.2" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular/pwa/node_modules/@angular-devkit/core": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.5.3.tgz", + "integrity": "sha512-dv4zIZ7Xc/vkio5FGHOxfSva/G9fU/w0uhF3vws3H5yjRVRgdmbQ22dTcLjVMPZBM359xy2KFK+4lFMGuEdLCw==", + "dependencies": { + "ajv": "~5.5.1", + "chokidar": "^1.7.0", + "rxjs": "^6.0.0-beta.3", + "source-map": "^0.5.6" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular/pwa/node_modules/@angular-devkit/schematics": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.5.3.tgz", + "integrity": "sha512-sAXaxeizGyadRb1jTtsyBwAMVLcGsGrmQiUw2aEf56uxnEWNNIntY5xPxP3ya0XrgUgG6YdrSNMpHay6qPGtww==", + "dependencies": { + "@angular-devkit/core": "0.5.3", + "@ngtools/json-schema": "^1.1.0", + "rxjs": "^6.0.0-beta.3" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular/pwa/node_modules/@schematics/angular": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.5.3.tgz", + "integrity": "sha512-RQ4DMA5lyJ5XtBLpFxFs0UkyStl1j4n0YAojm/MAGI3B7uKB8qTN0txYvCYrbBUPdGhZC8ic9qzUw9UAJfLfUA==", + "dependencies": { + "@angular-devkit/core": "0.5.3", + "@angular-devkit/schematics": "0.5.3", + "typescript": "~2.6.2" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular/pwa/node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "node_modules/@angular/pwa/node_modules/anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dependencies": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "node_modules/@angular/pwa/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dependencies": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.0.0" + } + }, + "node_modules/@angular/pwa/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@angular/pwa/node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==" + }, + "node_modules/@angular/pwa/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/@angular/pwa/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/@angular/pwa/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==" + }, + "node_modules/@angular/pwa/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/@angular/pwa/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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/@angular/pwa/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/readdirp/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular/pwa/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==" + }, + "node_modules/@angular/pwa/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/@angular/pwa/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/pwa/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@angular/pwa/node_modules/typescript": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", + "integrity": "sha512-L0QfAFYU8U/ucTqDptb0Hq67++OwqdSKDAAXmpaECxEkPOIpydxh4p0p9BRDG0kliZHFJBAWZDHR0nomxF/E7A==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@angular/router": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-14.2.11.tgz", + "integrity": "sha512-AbnyKXabar2WsG3fL24O1xdwkcRhRKI7u2vc9D8bcp2ks5GOJNxfbtG2Z6PSO18vtDszQxwELRe2cOEe+0TmPQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.2.11", + "@angular/core": "14.2.11", + "@angular/platform-browser": "14.2.11", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", + "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "dependencies": { + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz", + "integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", + "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", + "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "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.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "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.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "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-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "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-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", + "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.1", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.1", + "@babel/types": "^7.20.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", + "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", + "dependencies": { + "@babel/types": "^7.20.2", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", + "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "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, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "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, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz", + "integrity": "sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.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": "^12.22.0 || ^14.17.0 || >=16.0.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, + "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/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 + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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 + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", + "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "peer": true + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@ng-bootstrap/ng-bootstrap": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-13.1.1.tgz", + "integrity": "sha512-R6qnmFKT2EwwijBHw7rUXqyo5W90OImHOv7BlsxMNnZLIksWIhqwU00k4UBTfRTnd6JsTPuj/co3MaP61ajILA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^14.1.0", + "@angular/core": "^14.1.0", + "@angular/forms": "^14.1.0", + "@angular/localize": "^14.1.0", + "@popperjs/core": "^2.10.2", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngneat/svg-generator": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ngneat/svg-generator/-/svg-generator-6.0.0.tgz", + "integrity": "sha512-jHvkTlLyeNHcedVALyI0PiaLXt4CkLEW11FAsahXZTB8+v3McXquqqNAdHg1Gxyp1H1xvPDFf9j+KZ5xOIFJvw==", + "dev": true, + "dependencies": { + "camelcase": "6.2.0", + "chokidar": "3.5.3", + "commander": "9.4.1", + "cosmiconfig": "7.0.1", + "fs-extra": "10.1.0", + "glob": "8.0.3", + "lodash.kebabcase": "4.1.1", + "svgo": "2.8.0", + "typescript": "4.8.4" + }, + "bin": { + "svg-generator": "index.js" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@ngneat/svg-generator/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@ngneat/svg-icon": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ngneat/svg-icon/-/svg-icon-6.1.0.tgz", + "integrity": "sha512-9a3FuvhSBlJMBEk0HdESWWM5zReYE0VESjnwbNjLvEN64IaIjGk+mJlPzqX3y0WgBW8cx9+1/bnpAz/o+mHQhQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">= 14.0.0" + } + }, + "node_modules/@ngtools/json-schema": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz", + "integrity": "sha512-6i322P+RDn7nFiqdKqGGfvjyhEN551o8NsKxE0/lBAXzu0kUA8wlWa8d/RU1+VJtQ0+GOKFc7xHmoKuw7gt+Lw==", + "engines": { + "node": ">= 4.1.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/@ngtools/webpack": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.10.tgz", + "integrity": "sha512-sLHapZLVub6mEz5b19tf1VfIV1w3tYfg7FNPLeni79aldxu1FbP1v2WmiFAnMzrswqyK0bhTtxrl+Z/CLKqyoQ==", + "dev": true, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0", + "typescript": ">=4.6.2 <4.9", + "webpack": "^5.54.0" + } + }, + "node_modules/@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=13.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz", + "integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@ngx-translate/core": ">=14.0.0", + "rxjs": "^6.5.3 || ^7.4.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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz", + "integrity": "sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/git/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", + "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "dev": true, + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz", + "integrity": "sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/run-script/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@schematics/angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-14.2.10.tgz", + "integrity": "sha512-YFTc/9QJdx422XcApizEcVLKoyknu8b9zHIlAepZCu7WkV8GPT0hvVEHQ7KBWys5aQ7pPZMT0JpZLeAz0F2xYQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "jsonc-parser": "3.1.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.4.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", + "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", + "integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/type-utils": "5.43.0", + "@typescript-eslint/utils": "5.43.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", + "integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/typescript-estree": "5.43.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", + "integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/visitor-keys": "5.43.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", + "integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/utils": "5.43.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", + "integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", + "integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/visitor-keys": "5.43.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", + "integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/typescript-estree": "5.43.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", + "integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.43.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.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 + }, + "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 + }, + "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 + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "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, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "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, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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, + "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, + "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": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/agentkeepalive/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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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, + "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, + "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, + "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, + "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" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "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.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-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, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + } + ] + }, + "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, + "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 + }, + "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, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "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, + "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 + }, + "node_modules/bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "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 + }, + "node_modules/bootstrap": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.2.tgz", + "integrity": "sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", + "integrity": "sha512-Xx+xPlfCZIUHagysjjOAje9nRo8pRDczQCcXb4J2O0BLtH+xeVue6ba4y1kfJfQMAnM2mkcoMIAyOctlaRGWYA==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001431", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", + "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "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==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "node_modules/chart.js": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz", + "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==", + "peer": true, + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "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, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "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==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "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 + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "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 + }, + "node_modules/compression/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 + }, + "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 + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "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, + "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, + "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 + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "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==" + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.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==" + }, + "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, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + } + }, + "node_modules/critters/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/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, + "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/critters/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/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==", + "dev": true + }, + "node_modules/critters/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/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, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.1.0.tgz", + "integrity": "sha512-Sd99PrFgx28ez4GHu8yoQIufc/70h9oYowDf4EjeIKi8mac9whxRjhM3IaMr6EllP6KKKWtJrMfN6C7T9tIWvQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "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, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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 + }, + "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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-equal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz", + "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/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 + }, + "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 + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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 + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?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" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "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 + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "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==" + }, + "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, + "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, + "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, + "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, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "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, + "engines": { + "node": ">=6" + } + }, + "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 + }, + "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, + "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, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/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 + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz", + "integrity": "sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.15.5", + "esbuild-android-64": "0.15.5", + "esbuild-android-arm64": "0.15.5", + "esbuild-darwin-64": "0.15.5", + "esbuild-darwin-arm64": "0.15.5", + "esbuild-freebsd-64": "0.15.5", + "esbuild-freebsd-arm64": "0.15.5", + "esbuild-linux-32": "0.15.5", + "esbuild-linux-64": "0.15.5", + "esbuild-linux-arm": "0.15.5", + "esbuild-linux-arm64": "0.15.5", + "esbuild-linux-mips64le": "0.15.5", + "esbuild-linux-ppc64le": "0.15.5", + "esbuild-linux-riscv64": "0.15.5", + "esbuild-linux-s390x": "0.15.5", + "esbuild-netbsd-64": "0.15.5", + "esbuild-openbsd-64": "0.15.5", + "esbuild-sunos-64": "0.15.5", + "esbuild-windows-32": "0.15.5", + "esbuild-windows-64": "0.15.5", + "esbuild-windows-arm64": "0.15.5" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz", + "integrity": "sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz", + "integrity": "sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz", + "integrity": "sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz", + "integrity": "sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz", + "integrity": "sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz", + "integrity": "sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz", + "integrity": "sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz", + "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz", + "integrity": "sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz", + "integrity": "sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz", + "integrity": "sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz", + "integrity": "sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz", + "integrity": "sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz", + "integrity": "sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz", + "integrity": "sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz", + "integrity": "sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz", + "integrity": "sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.5.tgz", + "integrity": "sha512-lTJOEKekN/4JI/eOEq0wLcx53co2N6vaT/XjBz46D1tvIVoUEyM0o2K6txW6gEotf31szFD/J1PbxmnbkGlK9A==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz", + "integrity": "sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz", + "integrity": "sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz", + "integrity": "sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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 + }, + "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==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "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, + "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/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/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 + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/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, + "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/eslint/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/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==", + "dev": true + }, + "node_modules/eslint/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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 + }, + "node_modules/eslint/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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "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, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "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, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "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" + } + }, + "node_modules/express/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 + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "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, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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 + }, + "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, + "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 + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "engines": { + "node": ">=0.10.0" + } + }, + "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 + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "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.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "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==" + }, + "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 + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "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, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "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, + "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, + "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 + }, + "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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "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, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "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==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "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, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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==", + "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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/glob-base/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "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/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "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 + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "dependencies": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "node_modules/hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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 + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "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, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "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/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "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, + "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, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "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==", + "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==" + }, + "node_modules/ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/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, + "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/inquirer/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/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==", + "dev": true + }, + "node_modules/inquirer/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "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 + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "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==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "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 + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "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, + "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 + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jasmine-core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.3.0.tgz", + "integrity": "sha512-qybtBUesniQdW6n+QIHMng2vDOHscIC/dEXjW+JzO9+LoAZMb03RCUC5xFOv/btSKPm1xL42fn+RjlU4oB42Lg==", + "dev": true + }, + "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, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "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 + }, + "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 + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "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" + ] + }, + "node_modules/karma": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", + "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "dev": true, + "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.4.1", + "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, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", + "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.0.0.tgz", + "integrity": "sha512-SB8HNNiazAHXM1vGEzf8/tSyEhkfxuDdhYdPBX2Mwgzt0OuF2gicApQ+uvXLID/gXyJQgvrM9+1/2SxZFUUDIA==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "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, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/karma/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/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, + "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, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "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, + "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/karma/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, + "engines": { + "node": ">=10" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "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": "11.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz", + "integrity": "sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "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, + "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, + "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, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "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, + "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, + "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, + "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 + }, + "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, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "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 + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "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 + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/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, + "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/log-symbols/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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==", + "dev": true + }, + "node_modules/log-symbols/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", + "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.11.tgz", + "integrity": "sha512-GvsCITGAyDCxxsJ+X6prJexFQEhOCJaIlUbsAvjzSI5o5O7j2dle3jWvz5Z5aOdpOxW6ol3vI1+0ut+641F1+w==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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, + "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, + "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, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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 + }, + "node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "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 + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "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, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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 + }, + "node_modules/ng2-charts": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-4.1.1.tgz", + "integrity": "sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w==", + "dependencies": { + "lodash-es": "^4.17.15", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": ">=14.0.0", + "@angular/common": ">=14.0.0", + "@angular/core": ">=14.0.0", + "chart.js": "^3.4.0 || ^4.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "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, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.0.tgz", + "integrity": "sha512-A6rJWfXFz7TQNjpldJ915WFb1LnhO4lIve3ANPbWreuEoLoKlFT3sxIepPBkLhM27crW8YmN+pjlgbasH6cH/Q==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/node-gyp/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz", + "integrity": "sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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==", + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-install-checks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", + "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "node_modules/npm-package-arg": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.0.tgz", + "integrity": "sha512-4J0GL+u2Nh6OnhvUKXRr2ZMG4lR8qtLp+kv7UiV00Y+nGiSxtttCyIRHCt5L5BNkXQld/RceYItau3MDOoGiBw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist/node_modules/npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz", + "integrity": "sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz", + "integrity": "sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "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, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "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.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/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, + "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/ora/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/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==", + "dev": true + }, + "node_modules/ora/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", + "integrity": "sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg==", + "dev": true, + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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-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, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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==", + "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, + "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 + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "dependencies": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0" + }, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.10", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz", + "integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.0.0.tgz", + "integrity": "sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "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, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", + "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.0.tgz", + "integrity": "sha512-leqiqLOellpLKfbHkD06E04P6d9ZQ24mat6hu4NSqun7WG0UhspHR5Myiv/510qouCjoo4+YJtNOqg5xHaFnCA==", + "dev": true, + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.0.5", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.8", + "browserslist": "^4.21.3", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.0.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.8", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.10", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "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 + }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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==" + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "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, + "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, + "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, + "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, + "optional": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "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" + } + ] + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "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-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-package-json": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz", + "integrity": "sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-package-json/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "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 + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "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==", + "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, + "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 + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated" + }, + "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, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/rimraf/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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" + } + ] + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dependencies": { + "ret": "~0.1.10" + } + }, + "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 + }, + "node_modules/sass": { + "version": "1.54.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.4.tgz", + "integrity": "sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.0.2.tgz", + "integrity": "sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/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, + "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/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/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 + }, + "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 + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "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, + "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 + }, + "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, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/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==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "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, + "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, + "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, + "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, + "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 + }, + "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 + }, + "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 + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", + "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "node_modules/socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.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, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated" + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "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, + "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, + "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/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", + "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", + "dev": true, + "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, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/stylus-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.0.0.tgz", + "integrity": "sha512-WTbtLrNfOfLgzTaR9Lj/BPhQroKk/LC1hfTXSUbrxmxgfUo3Y3LpmKRVA2R1XbjvTAvOfaian9vOyfv1z99E+A==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "klona": "^2.0.5", + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "stylus": ">=0.52.4", + "webpack": "^5.0.0" + } + }, + "node_modules/stylus/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/stylus/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/stylus/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/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, + "engines": { + "node": ">= 10" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "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.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.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/terser-webpack-plugin/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, + "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/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/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 + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/test-exclude/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "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 + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.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==", + "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, + "engines": { + "node": ">=0.6" + } + }, + "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, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "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, + "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, + "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, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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 + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "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, + "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.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "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, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated" + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "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, + "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, + "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, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "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, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "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-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.0.tgz", + "integrity": "sha512-L5S4Q2zT57SK7tazgzjMiSMBdsw+rGYIX27MgPgx7LDhWO0lViPrHKoLS7jo5In06PWYAhlYu3PbyoC6yAThbw==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "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": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "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, + "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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/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 + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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, + "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, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/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==" + }, + "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==" + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dependencies": { + "cliui": "^7.0.2", + "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.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz", + "integrity": "sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==", + "dependencies": { + "tslib": "^2.3.0" + } + } + }, + "dependencies": { + "@adobe/css-tools": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", + "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@angular-devkit/architect": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.10.tgz", + "integrity": "sha512-/6YmPrgataj1jD2Uqd1ED+CG4DaZGacoeZd/89hH7hF76Nno8K18DrSOqJAEmDnOWegpSRGVLd0qP09IHmaG5w==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.2.10", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/build-angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.10.tgz", + "integrity": "sha512-VCeZAyq4uPCJukKInaSiD4i/GgxgcU4jFlLFQtoYNmaBS4xbPOymL19forRIihiV0dwNEa2L694vRTAPMBxIfw==", + "dev": true, + "requires": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/build-webpack": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@babel/core": "7.18.10", + "@babel/generator": "7.18.12", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.18.10", + "@babel/plugin-transform-async-to-generator": "7.18.6", + "@babel/plugin-transform-runtime": "7.18.10", + "@babel/preset-env": "7.18.10", + "@babel/runtime": "7.18.9", + "@babel/template": "7.18.10", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "14.2.10", + "ansi-colors": "4.1.3", + "babel-loader": "8.2.5", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.9.1", + "cacache": "16.1.2", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.1", + "esbuild": "0.15.5", + "esbuild-wasm": "0.15.5", + "glob": "8.0.3", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.0.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "mini-css-extract-plugin": "2.6.1", + "minimatch": "5.1.0", + "open": "8.4.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "6.0.1", + "piscina": "3.2.0", + "postcss": "8.4.16", + "postcss-import": "15.0.0", + "postcss-loader": "7.0.1", + "postcss-preset-env": "7.8.0", + "regenerator-runtime": "0.13.9", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.54.4", + "sass-loader": "13.0.2", + "semver": "7.3.7", + "source-map-loader": "4.0.0", + "source-map-support": "0.5.21", + "stylus": "0.59.0", + "stylus-loader": "7.0.0", + "terser": "5.14.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.4.0", + "webpack": "5.74.0", + "webpack-dev-middleware": "5.3.3", + "webpack-dev-server": "4.11.0", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1402.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.10.tgz", + "integrity": "sha512-h+2MaSY7QSvoJ3R+Hvin21jVCfPGOTLdASIUk4Jmq6J3y5BSku3KSSaV8dWoBOBkFCwQyPQMRjiHoHKLpC1K7g==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1402.10", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/core": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.10.tgz", + "integrity": "sha512-K4AO7mROTdbhQ7chtyQd6oPwmuL+BPUh+wn6Aq1qrmYJK4UZYFOPp8fi/Ehs8meCEeywtrssOPfrOE4Gsre9dg==", + "dev": true, + "requires": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/schematics": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.10.tgz", + "integrity": "sha512-MMp31KpJTwKHisXOq+6VOXYApq97hZxFaFmZk396X5aIFTCELUwjcezQDk+u2nEs5iK/COUfnN3plGcfJxYhQA==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.2.10", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-eslint/builder": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-14.4.0.tgz", + "integrity": "sha512-AhAUFvSg0urtb6Lsowvuxwu6DMXUy0BPwrnfNOBGjRt9vG7F9kgXXAsm5DnIS0GNy/mLZ9mSfa86fv++1e0KUA==", + "dev": true, + "requires": {} + }, + "@angular-eslint/bundled-angular-compiler": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-14.4.0.tgz", + "integrity": "sha512-KMHPHd24s0HVvAP/DxSSqhYBWhwW8FgS/r0Uwv8eWpsIdc/z/Chd2ush2SgPchmmquAXTgOZsbEY7ZmW+XkJfQ==", + "dev": true + }, + "@angular-eslint/eslint-plugin": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-14.4.0.tgz", + "integrity": "sha512-2rZQ4mt7tEUW+lI5jjuj3HWaT4VQtWTG6+LDnmuUmx76m8hqQ7NvFUpOcNDofu5KbEVBP+oF2DA6wjoZOIuSOA==", + "dev": true, + "requires": { + "@angular-eslint/utils": "14.4.0", + "@typescript-eslint/utils": "5.43.0" + } + }, + "@angular-eslint/eslint-plugin-template": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-14.4.0.tgz", + "integrity": "sha512-d3GM/EU2iWzr+BrITwO4gBf9WfDfuOdTjfinV/zN84oXMFaK2ENo+IP6OEsD9hh36rdPps+m2gFGDdx+rTzBpg==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "14.4.0", + "@angular-eslint/utils": "14.4.0", + "@typescript-eslint/type-utils": "5.43.0", + "@typescript-eslint/utils": "5.43.0", + "aria-query": "5.1.3", + "axobject-query": "3.1.1" + } + }, + "@angular-eslint/schematics": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-14.4.0.tgz", + "integrity": "sha512-BrGkPug+CZQWOfmNRsJDrEtYJcxvzF/kLlV7RjvIN9Ky5TjUiJVCeafl3VY6COSY32tjlh2GvBdl1AQKWWovbA==", + "dev": true, + "requires": { + "@angular-eslint/eslint-plugin": "14.4.0", + "@angular-eslint/eslint-plugin-template": "14.4.0", + "ignore": "5.2.0", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "dependencies": { + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, + "@angular-eslint/template-parser": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-14.4.0.tgz", + "integrity": "sha512-zq888KpQB0YTEK26mkKcT4fs8LDWWT1oAEXU8DrXhvkikS8XavTSHOWJye/bVZR4oJRFCF5YTJV75DEMcGNIpQ==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "14.4.0", + "eslint-scope": "^7.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "@angular-eslint/utils": { + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-14.4.0.tgz", + "integrity": "sha512-dPHklAVfh+JfueDfXre9Xooq7p5bFyKO2Z6y1agYeofAgHCPIJOPx2AhtFPrOtsc4VXFFiyE9XbowlXh4ogoKQ==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "14.4.0", + "@typescript-eslint/utils": "5.43.0" + } + }, + "@angular/animations": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-14.2.11.tgz", + "integrity": "sha512-HOw8xecbKfs7A5Ezjf+BfXKvvwU7X8I0US5Ey6bOuLvpA3QVOGSLw9BeutY5Q2mPWiRgnNNQW+FOd8Pe9gEkpQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/cdk": { + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.7.tgz", + "integrity": "sha512-/tEsYaUbDSnfEmKVvAMramIptmhI67O+9STjOV0i+74XR2NospeK0fkbywIANu1n3w6AHGMotvRWJrjmbCElFg==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + } + } + }, + "@angular/cli": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.2.10.tgz", + "integrity": "sha512-gX9sAKOwq4lKdPWeABB7TzKDHdjQXvkUU8NmPJA6mEAVXvm3lhQtFvHDalZstwK8au2LY0LaXTcEtcKYOt3AXQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1402.10", + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "@schematics/angular": "14.2.10", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "debug": "4.3.4", + "ini": "3.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "npm-package-arg": "9.1.0", + "npm-pick-manifest": "7.0.1", + "open": "8.4.0", + "ora": "5.4.1", + "pacote": "13.6.2", + "resolve": "1.22.1", + "semver": "7.3.7", + "symbol-observable": "4.0.0", + "uuid": "8.3.2", + "yargs": "17.5.1" + } + }, + "@angular/common": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.2.11.tgz", + "integrity": "sha512-a5w7lz4SoUzCwSDnuUPnfbEYPA8ufFiXz44mOv48P4uIb78q3rZ/R/EyWD1O3yJPof0o8aPNKpKZzuRDv3Q8ow==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-14.2.11.tgz", + "integrity": "sha512-QD4tq37qqPxxNK4o0Pd7dJm06evwEPChV67S/ecX3S6UkSDp8lVoWKiVx9htp/5s4iydKZU4eGu9oTOMOLVdOw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler-cli": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-14.2.11.tgz", + "integrity": "sha512-ipIEgueW8bhxVSq6qlgndBLVRCJoTvk1he/TI3w34m2EnZY1ctgGGCm1VbB3XARh+irVesPVMIAxRtjYds7XOw==", + "requires": { + "@babel/core": "^7.17.2", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.26.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + } + }, + "@angular/core": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.2.11.tgz", + "integrity": "sha512-4uEIA6ESMLt2f/ivKuVBpME0IbuFHWmpweN4dwJt83DfJBiBfpqdrFYZHz/Kbkh9cGCiP7L4/eKPRWTlAHehhw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/forms": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-14.2.11.tgz", + "integrity": "sha512-sItoA3/I8j/pf3zhv8sR37M5dAYUJpezv8rw2fTT2Y+nZJFUpkFWqX2N4qpMlPY0MP9OX++8K8/d2j0Lfi3wJQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/localize": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-14.2.11.tgz", + "integrity": "sha512-vrYv2f6BR1dKebZB+c0O4Arx7iGF1MqN4gHB+V5KHS7UPIc3YnoQn9LaFDRWWazjrXCvq1uf1Cb8qzzu19+lZQ==", + "requires": { + "@babel/core": "7.18.9", + "glob": "8.0.3", + "yargs": "^17.2.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.9.tgz", + "integrity": "sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.9", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@angular/platform-browser": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.11.tgz", + "integrity": "sha512-lGi9pF0Kf/GGrVKcfxxfStM2eMSluDTmbcYuVAX28iBn5XEdfsonrkfy2cnxUMnQ7nioMAZBNGOJHbQPKz4jwg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.2.11.tgz", + "integrity": "sha512-kjcZda+gcAiYd0I3mjLSr6xR/HkUCnmIMyqaFGoHnIDXI2c6wLDxi49pivrJFvUYJPfYAJ6GjlYTM6L9B3XSEQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/pwa": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@angular/pwa/-/pwa-0.5.3.tgz", + "integrity": "sha512-htgdGgWHaAHRdGZ1qxkn/4bQZjx2np+A/oGtuN86z+/jWuY4S+lYfPjLQTmq5kZaZDQm+qm0TVvayCQFEAnagg==", + "requires": { + "@angular-devkit/core": "0.5.3", + "@angular-devkit/schematics": "0.5.3", + "@schematics/angular": "0.5.3", + "typescript": "~2.6.2" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.5.3.tgz", + "integrity": "sha512-dv4zIZ7Xc/vkio5FGHOxfSva/G9fU/w0uhF3vws3H5yjRVRgdmbQ22dTcLjVMPZBM359xy2KFK+4lFMGuEdLCw==", + "requires": { + "ajv": "~5.5.1", + "chokidar": "^1.7.0", + "rxjs": "^6.0.0-beta.3", + "source-map": "^0.5.6" + } + }, + "@angular-devkit/schematics": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.5.3.tgz", + "integrity": "sha512-sAXaxeizGyadRb1jTtsyBwAMVLcGsGrmQiUw2aEf56uxnEWNNIntY5xPxP3ya0XrgUgG6YdrSNMpHay6qPGtww==", + "requires": { + "@angular-devkit/core": "0.5.3", + "@ngtools/json-schema": "^1.1.0", + "rxjs": "^6.0.0-beta.3" + } + }, + "@schematics/angular": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.5.3.tgz", + "integrity": "sha512-RQ4DMA5lyJ5XtBLpFxFs0UkyStl1j4n0YAojm/MAGI3B7uKB8qTN0txYvCYrbBUPdGhZC8ic9qzUw9UAJfLfUA==", + "requires": { + "@angular-devkit/core": "0.5.3", + "@angular-devkit/schematics": "0.5.3", + "typescript": "~2.6.2" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==", + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==" + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "requires": { + "kind-of": "^3.0.2" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==" + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "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" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "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==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "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==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "typescript": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", + "integrity": "sha512-L0QfAFYU8U/ucTqDptb0Hq67++OwqdSKDAAXmpaECxEkPOIpydxh4p0p9BRDG0kliZHFJBAWZDHR0nomxF/E7A==" + } + } + }, + "@angular/router": { + "version": "14.2.11", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-14.2.11.tgz", + "integrity": "sha512-AbnyKXabar2WsG3fL24O1xdwkcRhRKI7u2vc9D8bcp2ks5GOJNxfbtG2Z6PSO18vtDszQxwELRe2cOEe+0TmPQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", + "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==" + }, + "@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "requires": { + "@babel/compat-data": "^7.20.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz", + "integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", + "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/helper-wrap-function": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + } + }, + "@babel/helpers": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", + "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "requires": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.0" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", + "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "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.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz", + "integrity": "sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz", + "integrity": "sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz", + "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.18.10", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.9", + "@babel/plugin-transform-classes": "^7.18.9", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.18.9", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.9", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.9", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.10", + "babel-plugin-polyfill-corejs2": "^0.3.2", + "babel-plugin-polyfill-corejs3": "^0.5.3", + "babel-plugin-polyfill-regenerator": "^0.4.0", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@babel/traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", + "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.1", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.1", + "@babel/types": "^7.20.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", + "integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", + "requires": { + "@babel/types": "^7.20.2", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/types": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", + "integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@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 + }, + "@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + } + }, + "@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "requires": {} + }, + "@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "requires": {} + }, + "@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 + }, + "@esbuild/linux-loong64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz", + "integrity": "sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.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" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "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 + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", + "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "@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 + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@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 + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "peer": true + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@ng-bootstrap/ng-bootstrap": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-13.1.1.tgz", + "integrity": "sha512-R6qnmFKT2EwwijBHw7rUXqyo5W90OImHOv7BlsxMNnZLIksWIhqwU00k4UBTfRTnd6JsTPuj/co3MaP61ajILA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ngneat/svg-generator": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ngneat/svg-generator/-/svg-generator-6.0.0.tgz", + "integrity": "sha512-jHvkTlLyeNHcedVALyI0PiaLXt4CkLEW11FAsahXZTB8+v3McXquqqNAdHg1Gxyp1H1xvPDFf9j+KZ5xOIFJvw==", + "dev": true, + "requires": { + "camelcase": "6.2.0", + "chokidar": "3.5.3", + "commander": "9.4.1", + "cosmiconfig": "7.0.1", + "fs-extra": "10.1.0", + "glob": "8.0.3", + "lodash.kebabcase": "4.1.1", + "svgo": "2.8.0", + "typescript": "4.8.4" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, + "@ngneat/svg-icon": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ngneat/svg-icon/-/svg-icon-6.1.0.tgz", + "integrity": "sha512-9a3FuvhSBlJMBEk0HdESWWM5zReYE0VESjnwbNjLvEN64IaIjGk+mJlPzqX3y0WgBW8cx9+1/bnpAz/o+mHQhQ==", + "requires": { + "tslib": "^2.0.0" + } + }, + "@ngtools/json-schema": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz", + "integrity": "sha512-6i322P+RDn7nFiqdKqGGfvjyhEN551o8NsKxE0/lBAXzu0kUA8wlWa8d/RU1+VJtQ0+GOKFc7xHmoKuw7gt+Lw==" + }, + "@ngtools/webpack": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.10.tgz", + "integrity": "sha512-sLHapZLVub6mEz5b19tf1VfIV1w3tYfg7FNPLeni79aldxu1FbP1v2WmiFAnMzrswqyK0bhTtxrl+Z/CLKqyoQ==", + "dev": true, + "requires": {} + }, + "@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ngx-translate/http-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz", + "integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz", + "integrity": "sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/node-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", + "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "dev": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz", + "integrity": "sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + }, + "@schematics/angular": { + "version": "14.2.10", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-14.2.10.tgz", + "integrity": "sha512-YFTc/9QJdx422XcApizEcVLKoyknu8b9zHIlAepZCu7WkV8GPT0hvVEHQ7KBWys5aQ7pPZMT0JpZLeAz0F2xYQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.2.10", + "@angular-devkit/schematics": "14.2.10", + "jsonc-parser": "3.1.0" + } + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "@types/eslint": { + "version": "8.4.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", + "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "@types/express": { + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz", + "integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.31", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", + "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", + "integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/type-utils": "5.43.0", + "@typescript-eslint/utils": "5.43.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", + "integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/typescript-estree": "5.43.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", + "integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/visitor-keys": "5.43.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", + "integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/utils": "5.43.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", + "integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", + "integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/visitor-keys": "5.43.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", + "integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/typescript-estree": "5.43.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", + "integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.43.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@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 + }, + "@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 + }, + "@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 + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "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, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "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, + "requires": {} + }, + "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, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + } + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "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, + "requires": { + "ajv": "^8.0.0" + } + }, + "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, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "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 + }, + "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, + "requires": { + "type-fest": "^0.21.3" + } + }, + "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 + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==" + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "requires": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "bonjour-service": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.14.tgz", + "integrity": "sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "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 + }, + "bootstrap": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.2.tgz", + "integrity": "sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==", + "requires": {} + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "requires": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cacache": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.2.tgz", + "integrity": "sha512-Xx+xPlfCZIUHagysjjOAje9nRo8pRDczQCcXb4J2O0BLtH+xeVue6ba4y1kfJfQMAnM2mkcoMIAyOctlaRGWYA==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001431", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", + "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chart.js": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz", + "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==", + "peer": true, + "requires": { + "@kurkle/color": "^0.3.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "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, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "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==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "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 + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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 + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "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 + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "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==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "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, + "requires": { + "is-what": "^3.14.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==" + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "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, + "requires": { + "is-glob": "^4.0.3" + } + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dev": true, + "requires": { + "browserslist": "^4.21.4" + } + }, + "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==" + }, + "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, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + } + }, + "css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "requires": {} + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "cssdb": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.1.0.tgz", + "integrity": "sha512-Sd99PrFgx28ez4GHu8yoQIufc/70h9oYowDf4EjeIKi8mac9whxRjhM3IaMr6EllP6KKKWtJrMfN6C7T9tIWvQ==", + "dev": true + }, + "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 + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + } + }, + "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 + }, + "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 + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==" + }, + "deep-equal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz", + "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.8" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "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 + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "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 + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "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 + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "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, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "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 + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" + }, + "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==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "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 + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "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, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + } + }, + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "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, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "esbuild": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.5.tgz", + "integrity": "sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg==", + "dev": true, + "optional": true, + "requires": { + "@esbuild/linux-loong64": "0.15.5", + "esbuild-android-64": "0.15.5", + "esbuild-android-arm64": "0.15.5", + "esbuild-darwin-64": "0.15.5", + "esbuild-darwin-arm64": "0.15.5", + "esbuild-freebsd-64": "0.15.5", + "esbuild-freebsd-arm64": "0.15.5", + "esbuild-linux-32": "0.15.5", + "esbuild-linux-64": "0.15.5", + "esbuild-linux-arm": "0.15.5", + "esbuild-linux-arm64": "0.15.5", + "esbuild-linux-mips64le": "0.15.5", + "esbuild-linux-ppc64le": "0.15.5", + "esbuild-linux-riscv64": "0.15.5", + "esbuild-linux-s390x": "0.15.5", + "esbuild-netbsd-64": "0.15.5", + "esbuild-openbsd-64": "0.15.5", + "esbuild-sunos-64": "0.15.5", + "esbuild-windows-32": "0.15.5", + "esbuild-windows-64": "0.15.5", + "esbuild-windows-arm64": "0.15.5" + } + }, + "esbuild-android-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz", + "integrity": "sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz", + "integrity": "sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz", + "integrity": "sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz", + "integrity": "sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz", + "integrity": "sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz", + "integrity": "sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz", + "integrity": "sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz", + "integrity": "sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz", + "integrity": "sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz", + "integrity": "sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz", + "integrity": "sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz", + "integrity": "sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz", + "integrity": "sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz", + "integrity": "sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz", + "integrity": "sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz", + "integrity": "sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz", + "integrity": "sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA==", + "dev": true, + "optional": true + }, + "esbuild-wasm": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.15.5.tgz", + "integrity": "sha512-lTJOEKekN/4JI/eOEq0wLcx53co2N6vaT/XjBz46D1tvIVoUEyM0o2K6txW6gEotf31szFD/J1PbxmnbkGlK9A==", + "dev": true + }, + "esbuild-windows-32": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz", + "integrity": "sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz", + "integrity": "sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz", + "integrity": "sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA==", + "dev": true, + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "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 + }, + "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==" + }, + "eslint": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", + "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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 + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", + "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "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 + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "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, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "requires": { + "isarray": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "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 + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "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, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", + "requires": { + "is-extglob": "^1.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==" + } + } + }, + "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 + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "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==" + }, + "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 + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "requires": { + "for-in": "^1.0.1" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "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, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "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 + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "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==" + }, + "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==" + }, + "get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "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 + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "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==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "requires": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "hosted-git-info": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", + "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + } + }, + "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, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "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, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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 + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "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, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@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" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "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, + "requires": {} + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + } + }, + "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, + "optional": true + }, + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true + }, + "inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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 + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "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==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "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 + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "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==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "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 + }, + "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==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==" + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "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 + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "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 + } + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jasmine-core": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.3.0.tgz", + "integrity": "sha512-qybtBUesniQdW6n+QIHMng2vDOHscIC/dEXjW+JzO9+LoAZMb03RCUC5xFOv/btSKPm1xL42fn+RjlU4oB42Lg==", + "dev": true + }, + "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, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "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 + }, + "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 + }, + "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 + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + }, + "jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "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 + }, + "karma": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", + "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "dev": true, + "requires": { + "@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.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "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, + "requires": { + "minimist": "^1.2.6" + } + }, + "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 + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "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" + } + }, + "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 + } + } + }, + "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, + "requires": { + "which": "^1.2.1" + } + }, + "karma-coverage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", + "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "requires": { + "jasmine-core": "^4.1.0" + } + }, + "karma-jasmine-html-reporter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.0.0.tgz", + "integrity": "sha512-SB8HNNiazAHXM1vGEzf8/tSyEhkfxuDdhYdPBX2Mwgzt0OuF2gicApQ+uvXLID/gXyJQgvrM9+1/2SxZFUUDIA==", + "dev": true, + "requires": {} + }, + "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, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "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==" + }, + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "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", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "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, + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + }, + "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, + "optional": true + } + } + }, + "less-loader": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz", + "integrity": "sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==", + "dev": true, + "requires": { + "klona": "^2.0.4" + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "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, + "requires": { + "webpack-sources": "^3.0.0" + } + }, + "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 + }, + "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 + }, + "loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "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 + }, + "lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "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 + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log4js": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", + "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", + "dev": true, + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" + } + }, + "lru-cache": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", + "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==", + "dev": true + }, + "magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "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, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "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 + }, + "memfs": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.11.tgz", + "integrity": "sha512-GvsCITGAyDCxxsJ+X6prJexFQEhOCJaIlUbsAvjzSI5o5O7j2dle3jWvz5Z5aOdpOxW6ol3vI1+0ut+641F1+w==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "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 + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "optional": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "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 + }, + "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 + }, + "ng2-charts": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-4.1.1.tgz", + "integrity": "sha512-iHwXDbmX86lfeH8VRcsaW2tJATsuAZo4kvvC/Yk2l35zOHjevja1qBvO6BAibiDazi9r9aS6ZRJOqWPsz1pP2w==", + "requires": { + "lodash-es": "^4.17.15", + "tslib": "^2.3.0" + } + }, + "nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "optional": true, + "requires": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "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 + }, + "node-gyp": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.0.tgz", + "integrity": "sha512-A6rJWfXFz7TQNjpldJ915WFb1LnhO4lIve3ANPbWreuEoLoKlFT3sxIepPBkLhM27crW8YmN+pjlgbasH6cH/Q==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "optional": true + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "normalize-package-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz", + "integrity": "sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg==", + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "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 + }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", + "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.0.tgz", + "integrity": "sha512-4J0GL+u2Nh6OnhvUKXRr2ZMG4lR8qtLp+kv7UiV00Y+nGiSxtttCyIRHCt5L5BNkXQld/RceYItau3MDOoGiBw==", + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + } + }, + "npm-packlist": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz", + "integrity": "sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "dependencies": { + "npm-bundled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz", + "integrity": "sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^2.0.0" + } + }, + "npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true + } + } + }, + "npm-pick-manifest": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz", + "integrity": "sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg==", + "dev": true, + "requires": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz", + "integrity": "sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw==", + "dev": true, + "requires": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "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, + "requires": { + "boolbase": "^1.0.0" + } + }, + "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 + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "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 + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "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.3" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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 + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "dependencies": { + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", + "integrity": "sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg==", + "dev": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==" + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "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, + "requires": { + "@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" + } + }, + "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 + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "requires": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==" + }, + "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 + }, + "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==" + }, + "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 + }, + "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 + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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 + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "requires": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0", + "nice-napi": "^1.0.2" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==" + }, + "postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-properties": { + "version": "12.1.10", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.10.tgz", + "integrity": "sha512-U3BHdgrYhCrwTVcByFHs9EOBoqcKq4Lf3kXwbTi4hhq0qWhl/pDWq2THbv/ICX/Fl9KqeHBb8OVrTf2OaYF07A==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "requires": {} + }, + "postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "requires": {} + }, + "postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-import": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.0.0.tgz", + "integrity": "sha512-Y20shPQ07RitgBGv2zvkEAu9bqvrD77C9axhj/aA1BQj4czape2MdClCExvB27EwYEJdGgKZBpKanb0t1rK2Kg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "requires": {} + }, + "postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + } + }, + "postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "requires": {} + }, + "postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "requires": {} + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "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, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-opacity-percentage": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", + "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", + "dev": true + }, + "postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "requires": {} + }, + "postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-preset-env": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.0.tgz", + "integrity": "sha512-leqiqLOellpLKfbHkD06E04P6d9ZQ24mat6hu4NSqun7WG0UhspHR5Myiv/510qouCjoo4+YJtNOqg5xHaFnCA==", + "dev": true, + "requires": { + "@csstools/postcss-cascade-layers": "^1.0.5", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.8", + "browserslist": "^4.21.3", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.0.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.8", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.10", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "requires": {} + }, + "postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "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 + }, + "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 + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==" + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true + }, + "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==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "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, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "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, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "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 + } + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "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 + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "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 + }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "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 + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "read-package-json": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz", + "integrity": "sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "dependencies": { + "npm-normalize-package-bin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz", + "integrity": "sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ==", + "dev": true + } + } + }, + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "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 + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" + }, + "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==" + }, + "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 + }, + "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 + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==" + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "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 + } + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "requires": { + "tslib": "^2.1.0" + } + }, + "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 + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass": { + "version": "1.54.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.4.tgz", + "integrity": "sha512-3tmF16yvnBwtlPrNBHw/H907j8MlOX8aTBnlNX1yrKx24RKcJGPyLhFUwkoKBKesR3unP93/2z14Ll8NicwQUA==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.0.2.tgz", + "integrity": "sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + } + } + }, + "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 + }, + "selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "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==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "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, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "requires": { + "kind-of": "^6.0.2" + } + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "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 + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", + "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.0" + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dev": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "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, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "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 + }, + "streamroller": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", + "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", + "dev": true, + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + } + }, + "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, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "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 + }, + "stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "stylus-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.0.0.tgz", + "integrity": "sha512-WTbtLrNfOfLgzTaR9Lj/BPhQroKk/LC1hfTXSUbrxmxgfUo3Y3LpmKRVA2R1XbjvTAvOfaian9vOyfv1z99E+A==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "klona": "^2.0.5", + "normalize-path": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "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 + }, + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "dependencies": { + "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 + } + } + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.12.tgz", + "integrity": "sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "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==", + "requires": { + "is-number": "^7.0.0" + } + }, + "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 + }, + "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 + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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 + }, + "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, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "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 + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + }, + "ua-parser-js": { + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz", + "integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "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, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "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 + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "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 + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==" + } + } + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "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 + }, + "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 + }, + "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, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "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 + }, + "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 + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "webpack-dev-server": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.0.tgz", + "integrity": "sha512-L5S4Q2zT57SK7tazgzjMiSMBdsw+rGYIX27MgPgx7LDhWO0lViPrHKoLS7jo5In06PWYAhlYu3PbyoC6yAThbw==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "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, + "requires": { + "typed-assert": "^1.0.8" + } + }, + "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, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "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 + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "requires": { + "cliui": "^7.0.2", + "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.0.0" + } + }, + "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==" + }, + "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 + }, + "zone.js": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz", + "integrity": "sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==", + "requires": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/frontend/projects/admin/package.json b/frontend/projects/admin/package.json new file mode 100644 index 0000000000..945181718c --- /dev/null +++ b/frontend/projects/admin/package.json @@ -0,0 +1,71 @@ +{ + "name": "alfio-admin", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve --proxy-config proxy.config.json", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test", + "lint": "ng lint", + "prestart": "npm run svg", + "prebuild": "npm run svg", + "svg": "svg-generator" + }, + "svgGenerator": { + "outputPath": "./src/app/svg", + "srcPath": "./src/assets/svg", + "svgoConfig": { + "plugins": [ + "removeDimensions", + "cleanupAttrs" + ] + } + }, + "private": true, + "dependencies": { + "@angular/animations": "^14.2.0", + "@angular/cdk": "^14.2.7", + "@angular/common": "^14.2.0", + "@angular/compiler": "^14.2.0", + "@angular/core": "^14.2.0", + "@angular/forms": "^14.2.0", + "@angular/platform-browser": "^14.2.0", + "@angular/platform-browser-dynamic": "^14.2.0", + "@angular/pwa": "^0.5.3", + "@angular/router": "^14.2.0", + "@ng-bootstrap/ng-bootstrap": "^13.1.1", + "@ngneat/svg-icon": "^6.1.0", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "@popperjs/core": "^2.10.2", + "bootstrap": "^5.2.0", + "ng2-charts": "^4.1.1", + "rxjs": "~7.5.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.2.10", + "@angular-eslint/builder": "14.4.0", + "@angular-eslint/eslint-plugin": "14.4.0", + "@angular-eslint/eslint-plugin-template": "14.4.0", + "@angular-eslint/schematics": "14.4.0", + "@angular-eslint/template-parser": "14.4.0", + "@angular/cli": "~14.2.10", + "@angular/compiler-cli": "^14.2.0", + "@angular/localize": "^14.2.0", + "@ngneat/svg-generator": "^6.0.0", + "@types/jasmine": "~4.0.0", + "@typescript-eslint/eslint-plugin": "5.43.0", + "@typescript-eslint/parser": "5.43.0", + "eslint": "^8.28.0", + "jasmine-core": "~4.3.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.0.0", + "typescript": "~4.7.2" + } +} \ No newline at end of file diff --git a/frontend/projects/admin/proxy.config.json b/frontend/projects/admin/proxy.config.json new file mode 100644 index 0000000000..a708abab83 --- /dev/null +++ b/frontend/projects/admin/proxy.config.json @@ -0,0 +1,32 @@ +{ + "/authentication-status": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/authenticate": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/admin/api": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/api/v2/": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/file": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + } +} diff --git a/frontend/projects/admin/src/app/access-control/access-control.component.html b/frontend/projects/admin/src/app/access-control/access-control.component.html new file mode 100644 index 0000000000..46e0673e93 --- /dev/null +++ b/frontend/projects/admin/src/app/access-control/access-control.component.html @@ -0,0 +1 @@ +<p>access-control works!</p> diff --git a/frontend/projects/admin/src/app/access-control/access-control.component.scss b/frontend/projects/admin/src/app/access-control/access-control.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/access-control/access-control.component.ts b/frontend/projects/admin/src/app/access-control/access-control.component.ts new file mode 100644 index 0000000000..6101158ba7 --- /dev/null +++ b/frontend/projects/admin/src/app/access-control/access-control.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-access-control', + templateUrl: './access-control.component.html', + styleUrls: ['./access-control.component.scss'] +}) +export class AccessControlComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.html b/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.html new file mode 100644 index 0000000000..2c2c3a1791 --- /dev/null +++ b/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.html @@ -0,0 +1,108 @@ +<div class="container mt-5"> + <h1>Bulk creation</h1> + <hr /> +</div> + +<form [formGroup]="bulkForm" (ngSubmit)="save()"> + <div class="container d-flex justify-content-between mt-5"> + <div class="d-flex flex-fill"> + <label for="UserOrganizationId" class="d-flex me-3">Organization:</label> + <select + id="UserOrganizationId" + class="form-select form-control d-flex" + formControlName="organizationId" + [class.is-invalid]=" + userOrganizationId?.invalid && + (userOrganizationId?.dirty || userOrganizationId?.touched) + " + > + <option></option> + <option *ngFor="let org of organizations$ | async" [ngValue]="org.id"> + {{ org.name }} + </option> + </select> + </div> + + <div class="form-group d-flex flex-fill ms-3"> + <label for="userRole" class="d-flex me-3">Role:</label> + <select + id="userRole" + class="form-select" + formControlName="role" + [class.is-invalid]=" + userRole?.invalid && (userRole?.dirty || userRole?.touched) + " + > + <option *ngFor="let role of roles$ | async" [ngValue]="role.role"> + {{ role.description }} + </option> + </select> + </div> + </div> + <div class="container mt-5"> + <h4 class="mt-4 mb-4">File Specifications:</h4> + <h5><strong>General</strong></h5> + <span + >Please create a CSV file without header, using commas (,) as separator, + double quotes (") as quote character and backslash (\) as escaping + character</span + > + <h5 class="mt-4 mb-4"><strong>Row specification</strong></h5> + <pre> + <span class="text-info">name</span> + where: + <span class="text-info">name</span> + is the name of the client, to be displayed in the QR-Code and statistics + </pre> + <div class="file-upload-wrapper"> + <input + type="file" + id="input-file-now" + class="file-upload" + (change)="onFileSelected($event)" + /> + </div> + <table class="table table-striped" *ngIf="bulkDescriptions.length > 0"> + <thead> + <tr> + <th colspan="2">Name</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let description of bulkDescriptions"> + <td colspan="2"> + {{ description }} + </td> + </tr> + </tbody> + <tfoot> + <tr> + <th>Total</th> + <th> + {{ bulkDescriptions.length }} + </th> + </tr> + </tfoot> + </table> + + <div class="d-flex justify-content-between mt-5"> + <div> + <button + class="btn btn-lg btn-outline-secondary" + [routerLink]="['/access-control/api-keys']" + > + Cancel + </button> + </div> + <div> + <button + type="submit" + class="btn btn-lg btn-primary" + [disabled]="bulkForm.invalid || bulkDescriptions.length == 0" + > + Save + </button> + </div> + </div> + </div> +</form> diff --git a/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.scss b/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.ts b/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.ts new file mode 100644 index 0000000000..9eed03db96 --- /dev/null +++ b/frontend/projects/admin/src/app/api-key-system-bulk/api-key-system-bulk.component.ts @@ -0,0 +1,67 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Organization } from '../model/organization'; +import { Observable, map } from 'rxjs'; +import { Role } from '../model/role'; +import { OrganizationService } from '../shared/organization.service'; +import { UserService } from '../shared/user.service'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +@Component({ + selector: 'app-api-key-system-bulk', + templateUrl: './api-key-system-bulk.component.html', + styleUrls: ['./api-key-system-bulk.component.scss'], +}) +export class ApiKeySystemBulkComponent implements OnInit { + public organizations$?: Observable<Organization[]>; + public roles$?: Observable<Role[]>; + public bulkForm: FormGroup; + + constructor( + private readonly route: ActivatedRoute, + private readonly organizationService: OrganizationService, + private readonly userService: UserService, + private readonly router: Router, + formBuilder: FormBuilder + ) { + this.bulkForm = formBuilder.group({ + organizationId: [null, Validators.required], + role: [null, Validators.required], + descriptions: [[]], + }); + } + + get bulkDescriptions(): string[] { + return this.bulkForm.value.descriptions; + } + + get userOrganizationId(){return this.bulkForm.get('organizationId')} + get userRole(){return this.bulkForm.get('role')} + + ngOnInit(): void { + this.organizations$ = this.organizationService.getOrganizations(); + this.roles$ = this.userService.getAllRoles(); + } + + save(): void { + this.userService.createApiBulk(this.bulkForm.value).subscribe((res) => { + if (res === 'OK') { + this.router.navigate(['/access-control/api-keys']); + } + }); + } + + onFileSelected(event: Event) { + const input = event.target as HTMLInputElement; + if (input && input.files) { + const file: File = input.files[0]; + file.text().then((res) => { + const descriptions = res + .split('\n') + .map((es) => es.trim()) + .filter((es) => es !== null && es.length > 0); + this.bulkForm.get('descriptions')?.patchValue(descriptions); + }); + } + } +} diff --git a/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.html b/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.html new file mode 100644 index 0000000000..6d5527c506 --- /dev/null +++ b/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.html @@ -0,0 +1,79 @@ +<div class="container"> + <h1 translate="admin.api-key.new.title"></h1> + + <form [formGroup]="userForm" (ngSubmit)="save()"> + <div class="form-group my-3"> + <label + for="UserOrganizationId" + translate="admin.api-key.new.organization" + ></label> + + <select + id="UserOrganizationId" + class="form-select form-control" + formControlName="organizationId" + [class.is-invalid]=" + organizationName?.invalid && + (organizationName?.dirty || organizationName?.touched) + " + > + <option *ngFor="let org of organizations$ | async" [ngValue]="org.id"> + {{ org.name }} + </option> + </select> + </div> + + <div class="form-group my-3"> + <label for="userRole" translate="admin.api-key.new.role"></label> + + <select + id="userRole" + class="form-select" + formControlName="role" + [class.is-invalid]="role?.invalid && (role?.dirty || role?.touched)" + > + <option *ngFor="let role of roles$ | async" [ngValue]="role.role"> + {{ role.description }} + </option> + </select> + </div> + + <div class="form-group my-3"> + <label + for="organizationDescription" + translate="admin.api-key.new.description" + > + </label> + + <input + type="text" + id="organizationDescription" + class="form-control" + formControlName="description" + [class.is-invalid]=" + organizationDescription?.invalid && + (organizationDescription?.dirty || organizationDescription?.touched) + " + /> + </div> + + <div class="d-flex justify-content-between"> + <div> + <button + class="btn btn-lg btn-outline-secondary" + [routerLink]="['/access-control/users']" + translate="admin.common.cancel" + ></button> + </div> + + <div> + <button + type="submit" + class="btn btn-lg btn-primary" + [disabled]="userForm.invalid" + translate="admin.common.save" + ></button> + </div> + </div> + </form> +</div> diff --git a/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.scss b/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.ts b/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.ts new file mode 100644 index 0000000000..6c89cd1a40 --- /dev/null +++ b/frontend/projects/admin/src/app/api-key-system-edit/api-key-system-edit.component.ts @@ -0,0 +1,86 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Organization } from '../model/organization'; +import { Observable, map, of } from 'rxjs'; +import { User } from '../model/user'; +import { OrganizationService } from '../shared/organization.service'; +import { UserService } from '../shared/user.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Role } from '../model/role'; + +@Component({ + selector: 'app-api-key-system-edit', + templateUrl: './api-key-system-edit.component.html', + styleUrls: ['./api-key-system-edit.component.scss'], +}) +export class ApiKeySystemEditComponent implements OnInit { + public userForm: FormGroup; + public editMode: boolean | undefined; + public organizations$?: Observable<Organization[]>; + public user$: Observable<User | null> = of(); + public roles$?: Observable<Role[]>; + public userId: string | null = null; + + constructor( + formBuilder: FormBuilder, + private readonly organizationService: OrganizationService, + private readonly userService: UserService, + private readonly router: Router, + private readonly route: ActivatedRoute + ) { + this.userForm = formBuilder.group({ + id: [null], + organizationId: [null, Validators.required], + role: [null, Validators.required], + description: [null], + username: [null], + lastName: [null], + firstName: [null], + emailAddress: [null], + type: ['API_KEY'], + }); + } + get organizationDescription() { + return this.userForm.get('description'); + } + + get organizationName() { + return this.userForm.get('organizationId'); + } + + get role() { + return this.userForm.get('role'); + } + + ngOnInit(): void { + this.userId = this.route.snapshot.paramMap.get('userId'); + this.organizations$ = this.organizationService.getOrganizations(); + this.roles$ = this.userService.getAllRoles().pipe( + map((roles) => { + return roles.filter((role) => role.target.includes('USER')); + }) + ); + + if (this.userId !== null) { + this.editMode = true; + this.user$ = this.userService.getUser(this.userId); + this.user$.subscribe((user) => { + if (user) this.userForm.patchValue(user); + }); + } else { + this.editMode = false; + } + } + + save(): void { + let result: Observable<any>; + if (this.editMode) { + result = this.userService.update(this.userForm.value); + } else { + result = this.userService.create(this.userForm.value); + } + result.subscribe((res) => { + this.router.navigate(['/access-control/api-keys']); + }); + } +} diff --git a/frontend/projects/admin/src/app/api-key-system/api-key-system.component.html b/frontend/projects/admin/src/app/api-key-system/api-key-system.component.html new file mode 100644 index 0000000000..6e8e4f3709 --- /dev/null +++ b/frontend/projects/admin/src/app/api-key-system/api-key-system.component.html @@ -0,0 +1,128 @@ +<div class="container"> + <h1 class="my-3" translate="admin.api-key.list.title"></h1> + <p translate="admin.api-key.list.text"></p> + <hr /> + + <div class="d-flex py-3"> + <div ngbDropdown class="d-inline-block d-flex mx-3"> + <button + type="button" + class="btn btn-outline-primary" + id="dropdownBasic1" + ngbDropdownToggle + translate="admin.api-key.list.download-api-keys" + ></button> + <div ngbDropdownMenu aria-labelledby="dropdownBasic1"> + <button + (click)="downloadAllApiKeys(org.id)" + *ngFor="let org of organizations$ | async" + ngbDropdownItem + > + {{ org.name }} + </button> + </div> + <a + class="btn btn-warning mx-3" + [routerLink]="['/access-control/api-keys/bulk']" + > + <svg-icon key="groups" size="lg"></svg-icon> + <span translate="admin.api-key.list.bulk-creation"></span> + </a> + <button + type="button" + class="btn btn-success d-flex align-items-center mx-3" + [routerLink]="['/access-control/api-keys/new']" + > + <svg-icon + key="add" + size="lg" + class="d-flex align-items-center" + ></svg-icon> + <span translate="admin.common.add-new"></span> + </button> + </div> + </div> + + <div class="container"> + <table class="table table-striped"> + <thead> + <tr> + <th scope="col" translate="admin.api-key.list.api-key"></th> + <th scope="col" translate="admin.api-key.list.description"></th> + <th scope="col" translate="admin.api-key.list.role"></th> + <th scope="col" translate="admin.api-key.list.member-of"></th> + <th scope="col" translate="admin.api-key.list.qr-code"></th> + <th scope="col" translate="common.edit"></th> + <th scope="col"> + <span translate="admin.api-key.list.enabled"></span>/<span + translate="admin.api-key.list.disable" + ></span> + </th> + <th scope="col" translate="admin.common.delete"></th> + </tr> + </thead> + <tbody> + <tr + *ngFor=" + let user of users$ | async | usersFilterOrg : selectedOrganization + " + > + <td>{{ user.username }}</td> + <td> + <span *ngFor="let org of user.memberOf"> + {{ org.description }} + </span> + </td> + <td> + <span *ngFor="let role of user.roles">{{ + roleDescription(role) | async + }}</span> + </td> + <td> + <span *ngFor="let org of user.memberOf" class="badge bg-primary">{{ + org.name + }}</span> + </td> + <td>view qr code</td> + <td> + <a + [routerLink]="['/access-control/api-keys', user.id, 'edit']" + class="btn" + *ngIf="user.enabled" + (click)="enable(user)" + ><svg-icon key="edit" size="lg"></svg-icon + ></a> + </td> + <td> + <button + *ngIf="user.enabled" + class="btn btn-warning" + (click)="enable(user)" + > + <svg-icon key="visibilityoff" size="lg"></svg-icon> + <span translate="admin.api-key.list.disable"></span> + </button> + <button + *ngIf="!user.enabled" + class="btn btn-success" + (click)="enable(user)" + > + <svg-icon key="visibility-on" size="lg"></svg-icon> + <span translate="admin.api-key.list.enabled"></span> + </button> + </td> + + <td> + <button + class="btn btn-danger text-nowrap" + (click)="deleteUserApikey(user)" + > + <svg-icon key="delete" size="lg"></svg-icon> + <span translate="admin.common.delete"></span> + </button> + </td> + </tr> + </tbody> + </table> + </div> +</div> diff --git a/frontend/projects/admin/src/app/api-key-system/api-key-system.component.scss b/frontend/projects/admin/src/app/api-key-system/api-key-system.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/api-key-system/api-key-system.component.ts b/frontend/projects/admin/src/app/api-key-system/api-key-system.component.ts new file mode 100644 index 0000000000..cbe84d3acd --- /dev/null +++ b/frontend/projects/admin/src/app/api-key-system/api-key-system.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { UserService } from '../shared/user.service'; +import { Observable, map, of, shareReplay } from 'rxjs'; +import { RoleType, User } from '../model/user'; +import { Organization } from '../model/organization'; +import { Role } from '../model/role'; + +@Component({ + selector: 'app-api-key-system', + templateUrl: './api-key-system.component.html', + styleUrls: ['./api-key-system.component.scss'], +}) +export class ApiKeySystemComponent implements OnInit { + public users$?: Observable<User[]>; + public organizations$?: Observable<Organization[]>; + public roles$: Observable<Map<RoleType, Role>> = of(); + public selectedOrganization?: Organization; + + constructor(private readonly userService: UserService) {} + + ngOnInit(): void { + this.loadApiKey(); + this.roles$ = this.userService.getAllRoles().pipe( + map((roles) => { + const map: Map<RoleType, Role> = new Map(); + roles.forEach((role) => { + map.set(role.role, role); + }); + return map; + }), + shareReplay(1) + ); + } + + private loadApiKey() { + this.users$ = this.userService.getAllApiKey(); + this.users$.subscribe((users) => { + const orgIdToOrg = new Map<number, Organization>(); + users.forEach((user) => { + user.memberOf.forEach((org) => { + orgIdToOrg.set(org.id, org); + }); + }); + + const organizations = Array.from(orgIdToOrg.values()); + this.organizations$ = of(organizations); + }); + } + + roleDescription(role: RoleType): Observable<string | undefined> { + return this.roles$.pipe(map((roles) => roles.get(role)?.description)); + } + + enable(user: User) { + this.userService.enable(user, !user.enabled).subscribe((result) => { + this.loadApiKey(); + }); + } + + deleteUserApikey(user: User) { + if ( + window.confirm( + `The apikey ${user.username} will be deleted. Are you sure?` + ) + ) { + this.userService.deleteUser(user).subscribe((result) => { + this.loadApiKey(); + }); + } + } + + downloadAllApiKeys(orgId: number) { + window.open(`/admin/api/api-keys/organization/${orgId}/all`); + } +} diff --git a/frontend/projects/admin/src/app/app-routing.module.ts b/frontend/projects/admin/src/app/app-routing.module.ts new file mode 100644 index 0000000000..b29394ba70 --- /dev/null +++ b/frontend/projects/admin/src/app/app-routing.module.ts @@ -0,0 +1,85 @@ +import { NgModule } from '@angular/core'; +import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; +import { MissingOrgComponent } from './missing-org/missing-org.component'; +import { OrganizationsComponent } from './organizations/organizations.component'; +import { OrganizationEditComponent } from './organization-edit/organization-edit.component'; +import { AccessControlComponent } from './access-control/access-control.component'; +import { UserSystemComponent } from './user-system/user-system.component'; +import { UserSystemEditComponent } from './user-system-edit/user-system-edit.component'; +import { ApiKeySystemEditComponent } from './api-key-system-edit/api-key-system-edit.component'; +import { ApiKeySystemBulkComponent } from './api-key-system-bulk/api-key-system-bulk.component'; +import { ApiKeySystemComponent } from './api-key-system/api-key-system.component'; + +const routes: Routes = [ + { + path: 'organization/:organizationId/event/:eventId', + loadChildren: () => + import('./event/event.module').then((m) => m.EventModule), + }, + { + path: 'organization/:organizationId', + loadChildren: () => + import('./dashboard/dashboard.module').then((m) => m.DashboardModule), + }, + { + path: 'authentication', + loadChildren: () => + import('./authentication/authentication.module').then( + (m) => m.AuthenticationModule + ), + }, + { + path: 'organizations', + component: OrganizationsComponent, + }, + { + path: 'organizations/new', + component: OrganizationEditComponent, + }, + { + path: 'organizations/:organizationId/edit', + component: OrganizationEditComponent, + }, + { + path: 'access-control', + component: AccessControlComponent, + }, + { + path: 'access-control/users', + component: UserSystemComponent, + }, + { + path: 'access-control/users/new', + component: UserSystemEditComponent, + }, + { + path: 'access-control/users/:userId/edit', + component: UserSystemEditComponent, + }, + { path: 'access-control/api-keys', component: ApiKeySystemComponent }, + { + path: 'access-control/api-keys/new', + component: ApiKeySystemEditComponent, + }, + { + path: 'access-control/api-keys/:userId/edit', + component: ApiKeySystemEditComponent, + }, + { + path: 'access-control/api-keys/bulk', + component: ApiKeySystemBulkComponent, + }, + { + path: '', + component: MissingOrgComponent, + pathMatch: 'full', + }, +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/frontend/projects/admin/src/app/app.component.html b/frontend/projects/admin/src/app/app.component.html new file mode 100644 index 0000000000..675f96d6dc --- /dev/null +++ b/frontend/projects/admin/src/app/app.component.html @@ -0,0 +1,86 @@ +<!--based on https://getbootstrap.com/docs/5.2/examples/dashboard/ --> +<header class="navbar sticky-top navbar-light bg-light border-bottom flex-md-nowrap p-0"> + <div class="container-fluid justify-content-start"> + <a class="navbar-brand" [routerLink]="['./']"> + <img src="assets/alfio-logo.svg" height="42" alt="Alf.io logo"> + </a> + <form class="d-flex"> + <button class="btn btn-light" (click)="openOrgSelector()" type="button"> + <svg-icon key="organization" size="lg"></svg-icon> + {{(currentOrganization$ | async)?.name}} + <svg-icon key="arrowdropdown"></svg-icon> + </button> + </form> + + <div class="d-flex flex-grow-1 justify-content-end"> + <div ngbDropdown class="d-inline-block profile-menu-container"> + <button type="button" class="btn" id="profileBtn" ngbDropdownToggle> + <svg-icon key="accountcircle" size="lg"></svg-icon> {{(currentUser$ | async) | userName}} + </button> + <div ngbDropdownMenu aria-labelledby="profileBtn"> + <button type="button" ngbDropdownItem>Sign out</button> + </div> + </div> + </div> + +<!-- <input class="form-control form-control-dark w-100 rounded-0 border-0" type="text" placeholder="Search"--> +<!-- aria-label="Search">--> + + </div> +</header> +<div class="container-fluid"> + <div class="row"> + <nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse"> + <div class="position-sticky pt-3 sidebar-sticky" *ngIf="organizationId$ | async as organizationId"> + + <a class="d-flex flex-column gap-1 justify-content-center menu-item" [routerLink]="['/organization/', organizationId, 'event']" routerLinkActive="active"> + <svg-icon key="home"></svg-icon> <span translate="admin.menu.events"></span> + </a> + + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/organization/', organizationId, 'subscriptions']" routerLinkActive="active"> + <svg-icon key="subscription"></svg-icon> <span translate="admin.menu.subscriptions"></span> + </a> + + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/organization/', organizationId, 'organization-info']" routerLinkActive="active"> + <svg-icon key="organization"></svg-icon> <span translate="admin.menu.organization"></span> + </a> + + + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/organization/', organizationId, 'access-control']" routerLinkActive="active"> + <svg-icon key="access"></svg-icon> <span translate="admin.menu.security"></span> + </a> + + + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/organization/', organizationId, 'groups']" routerLinkActive="active"> + <svg-icon key="groups"></svg-icon> <span translate="admin.menu.groups"></span> + </a> + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/organization/', organizationId, 'configuration']" routerLinkActive="active"> + <svg-icon key="settings"></svg-icon> <span translate="admin.menu.settings"></span> + </a> + <ng-container *ngIf="(currentUser$ | async)?.role === 'ADMIN'"> + <hr class="mt-5"> + <p class="text-center text-muted mb-4" translate="admin.menu.system"></p> + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/organizations']" routerLinkActive="active"> + <svg-icon key="organization"></svg-icon> <span translate="admin.menu.system.organizations"></span> + </a> + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/access-control']" routerLinkActive="active"> + <svg-icon key="access"></svg-icon> <span translate="admin.menu.system.security"></span> + </a> + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/access-control/users']" routerLinkActive="active"> + <svg-icon key="groups"></svg-icon> <span>Users</span> + </a> + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/access-control/api-keys']" routerLinkActive="active"> + <svg-icon key="key"></svg-icon> <span>Api Keys</span> + </a> + + <a class="d-flex flex-column gap-1 justify-content-center" [routerLink]="['/configuration']" routerLinkActive="active"> + <svg-icon key="settings"></svg-icon> <span translate="admin.menu.system.settings"></span> + </a> + </ng-container> + </div> + </nav> + <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4"> + <router-outlet></router-outlet> + </main> + </div> +</div> diff --git a/frontend/projects/admin/src/app/app.component.scss b/frontend/projects/admin/src/app/app.component.scss new file mode 100644 index 0000000000..ed3615d9b8 --- /dev/null +++ b/frontend/projects/admin/src/app/app.component.scss @@ -0,0 +1,81 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/variables"; + +:host { + --header-height: 58px; +} + +header { + height: var(--header-height); +} + +/* + * Sidebar + */ + +.sidebar { + position: fixed; + top: 0; + /* rtl:raw: + right: 0; + */ + bottom: 0; + /* rtl:remove */ + left: 0; + z-index: 100; /* Behind the navbar */ + padding: var(--header-height) 0 0; /* Height of navbar */ + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1); +} + +.sidebar-sticky { + height: calc(100vh - var(--header-height)); + overflow-x: hidden; + overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ +} + +$sidebarWidth: 76px; + +nav#sidebarMenu { + @include media-breakpoint-up(md) { + width: $sidebarWidth; + } + + a { + text-decoration: none; + color: $text-muted; + font-size: 70%; + font-weight: normal; + text-align: center; + margin-bottom: 1rem; + + &.active { + color: $primary; + font-weight: bold; + } + + &:hover { + color: $primary; + } + + svg-icon, + svg { + display: flex; + align-self: center; + } + } +} + +main { + @include media-breakpoint-up(md) { + width: calc(100% - $sidebarWidth); + } +} + +@include media-breakpoint-up(md) { + .profile-menu-container .dropdown-menu[data-bs-popper] { + width: 100%; + right: 0; + left: unset; + } +} diff --git a/frontend/projects/admin/src/app/app.component.ts b/frontend/projects/admin/src/app/app.component.ts new file mode 100644 index 0000000000..9304cca9f3 --- /dev/null +++ b/frontend/projects/admin/src/app/app.component.ts @@ -0,0 +1,72 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivationEnd, Router } from '@angular/router'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; +import { combineLatest, filter, map, Observable, of } from 'rxjs'; +import { Organization } from './model/organization'; +import { UserInfo } from './model/user'; +import { OrgSelectorComponent } from './org-selector/org-selector.component'; +import { OrganizationService } from './shared/organization.service'; +import { UserService } from './shared/user.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], +}) +export class AppComponent implements OnInit { + public organizations$?: Observable<Organization[]>; + public organizationId$?: Observable<string | null>; + public currentUser$?: Observable<UserInfo>; + public currentOrganization$?: Observable<Organization | undefined>; + + constructor( + private readonly translateService: TranslateService, + private readonly organizationService: OrganizationService, + private readonly userService: UserService, + private readonly router: Router, + private readonly modalService: NgbModal + ) {} + + ngOnInit(): void { + this.translateService.setDefaultLang('en'); + this.translateService.use('en'); + this.organizations$ = this.organizationService.getOrganizations(); + this.organizationId$ = this.router.events.pipe( + filter((a) => a instanceof ActivationEnd), + map((a) => { + const ae = a as ActivationEnd; + return ae.snapshot.params['organizationId']; + }) + ); + this.currentUser$ = this.userService.getCurrent(); + this.currentOrganization$ = combineLatest([ + this.organizationId$, + this.organizations$, + ]).pipe( + map(([id, orgs]) => + orgs.find((o) => id !== null && o.id === Number.parseInt(id)) + ) + ); + } + + public openOrgSelector(): void { + const modalRef = this.modalService.open(OrgSelectorComponent, { + size: 'lg', + }); + const selector: OrgSelectorComponent = modalRef.componentInstance; + selector.organizations$ = this.organizations$; + selector.organizationId$ = this.organizationId$; + modalRef.result + .then((res: Organization) => { + this.router.navigate(['/organization', res.id, 'event']).then((r) => { + if (r) { + this.currentOrganization$ = of(res); + } + }); + }) + .catch(() => { + // we do nothing on dismiss + }); + } +} diff --git a/frontend/projects/admin/src/app/app.module.ts b/frontend/projects/admin/src/app/app.module.ts new file mode 100644 index 0000000000..4cfd2e5491 --- /dev/null +++ b/frontend/projects/admin/src/app/app.module.ts @@ -0,0 +1,110 @@ +import { + HTTP_INTERCEPTORS, + HttpClient, + HttpClientModule, + HttpClientXsrfModule, +} from '@angular/common/http'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { Router } from '@angular/router'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { SvgIconComponent, provideSvgIconsConfig } from '@ngneat/svg-icon'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { AlfioCommonModule } from 'common'; +import { firstValueFrom } from 'rxjs'; +import { AccessControlComponent } from './access-control/access-control.component'; +import { ApiKeySystemBulkComponent } from './api-key-system-bulk/api-key-system-bulk.component'; +import { ApiKeySystemEditComponent } from './api-key-system-edit/api-key-system-edit.component'; +import { ApiKeySystemComponent } from './api-key-system/api-key-system.component'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { AuthenticationModule } from './authentication/authentication.module'; +import { MissingOrgComponent } from './missing-org/missing-org.component'; +import { OrgSelectorComponent } from './org-selector/org-selector.component'; +import { OrganizationEditComponent } from './organization-edit/organization-edit.component'; +import { OrganizationsComponent } from './organizations/organizations.component'; +import { + HttpLoginInterceptor, + redirectToLogin, +} from './shared/http-login.interceptor'; +import { CustomLoader } from './shared/i18n.service'; +import { ICON_CONFIG } from './shared/icons'; +import { OrganizationService } from './shared/organization.service'; +import { SectionDashboardComponent } from './shared/section-dashboard/section-dashboard.component'; +import { SharedModule } from './shared/shared.module'; +import { UserService } from './shared/user.service'; +import { UserSystemEditComponent } from './user-system-edit/user-system-edit.component'; +import { UserSystemComponent } from './user-system/user-system.component'; + +export function RedirectToLoginIfNeeded( + userService: UserService, + router: Router +): () => Promise<boolean> { + return async () => { + const loggedIn = await firstValueFrom(userService.checkUserLoggedIn()); + if (!loggedIn) { + return redirectToLogin(router); + } + return true; + }; +} + +// AoT requires an exported function for factories +export function HttpLoaderFactory(http: HttpClient) { + return new CustomLoader(http); +} + +@NgModule({ + declarations: [ + AppComponent, + MissingOrgComponent, + OrgSelectorComponent, + OrganizationsComponent, + OrganizationEditComponent, + AccessControlComponent, + UserSystemComponent, + UserSystemEditComponent, + ApiKeySystemComponent, + ApiKeySystemEditComponent, + ApiKeySystemBulkComponent, + ], + imports: [ + BrowserModule, + AppRoutingModule, + AuthenticationModule, + HttpClientModule, + ReactiveFormsModule, + FormsModule, + HttpClientXsrfModule.withOptions({ + cookieName: 'XSRF-TOKEN', + headerName: 'X-CSRF-TOKEN', + }), + SvgIconComponent, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient], + }, + }), + NgbDropdownModule, + SharedModule, + AlfioCommonModule, + ], + providers: [ + { + provide: APP_INITIALIZER, + useFactory: RedirectToLoginIfNeeded, + deps: [UserService, Router], + multi: true, + }, + { provide: HTTP_INTERCEPTORS, useClass: HttpLoginInterceptor, multi: true }, + provideSvgIconsConfig(ICON_CONFIG), + UserService, + OrganizationService, + ], + exports: [SectionDashboardComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} diff --git a/frontend/projects/admin/src/app/authentication/authentication.component.html b/frontend/projects/admin/src/app/authentication/authentication.component.html new file mode 100644 index 0000000000..085c05de63 --- /dev/null +++ b/frontend/projects/admin/src/app/authentication/authentication.component.html @@ -0,0 +1,4 @@ +<div class="container text-center mt-5"> + <h1>Authentication requested</h1> + <p>Please authenticate on the backend before continuing</p> +</div> diff --git a/frontend/projects/admin/src/app/authentication/authentication.component.ts b/frontend/projects/admin/src/app/authentication/authentication.component.ts new file mode 100644 index 0000000000..e12472ffbf --- /dev/null +++ b/frontend/projects/admin/src/app/authentication/authentication.component.ts @@ -0,0 +1,8 @@ +import {Component} from "@angular/core"; + +@Component({ + selector: 'app-authentication', + templateUrl: './authentication.component.html' +}) +export class AuthenticationComponent { +} diff --git a/frontend/projects/admin/src/app/authentication/authentication.module.ts b/frontend/projects/admin/src/app/authentication/authentication.module.ts new file mode 100644 index 0000000000..958b7a0ba2 --- /dev/null +++ b/frontend/projects/admin/src/app/authentication/authentication.module.ts @@ -0,0 +1,22 @@ +import {NgModule} from "@angular/core"; +import {AuthenticationComponent} from "./authentication.component"; +import {ReactiveFormsModule} from "@angular/forms"; +import {RouterModule} from "@angular/router"; +import {HttpClientModule} from "@angular/common/http"; + +@NgModule({ + imports: [ + ReactiveFormsModule, + HttpClientModule, + RouterModule.forChild([ + { + path: '', + component: AuthenticationComponent + } + ]) + ], + declarations: [ + AuthenticationComponent + ] +}) +export class AuthenticationModule {} diff --git a/frontend/projects/admin/src/app/dashboard/dashboard.component.html b/frontend/projects/admin/src/app/dashboard/dashboard.component.html new file mode 100644 index 0000000000..29b1c428be --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/dashboard.component.html @@ -0,0 +1,64 @@ +<div class="container"> + <div class="my-3 d-flex justify-content-between d-flex align-items-center"> + <h1 translate="admin.dashboard.my-events"></h1> + <div> + <app-filter-button + text="Active" + [checked]="activeFilter" + (toggleFilter)="toggleActiveFilter($event)" + class="mx-2" + ></app-filter-button> + <app-filter-button + text="Past" + [checked]="inactiveFilter" + (toggleFilter)="toggleInactiveFilter($event)" + ></app-filter-button> + </div> + </div> + <hr /> + <div class="d-flex flex-row-reverse my-3"> + <button + type="button" + class="btn btn-success d-flex" + [routerLink]="" + translate="admin.common.add-new" + > + <svg-icon key="add" class="d-flex align-items-center"></svg-icon> + </button> + <button + type="button" + class="btn btn-secondary d-flex mx-2" + (click)="openExportDateSelector()" + translate="admin.dashboard.export-reservations" + > + <svg-icon key="download" class="d-flex align-items-center"></svg-icon> + </button> + </div> + + <ng-container *ngIf="activeFilter"> + <div class="card"> + <h2 class="card-header" translate="admin.dashboard.active"></h2> + <ul class="list-group list-group-flush"> + <li *ngFor="let ev of activeEvents$ | async" class="list-group-item"> + <app-event-item + [showImage]="((activeEvents$ | async)?.length || 0) <= 10" + [event]="ev" + ></app-event-item> + </li> + </ul> + </div> + </ng-container> + <ng-container *ngIf="inactiveFilter"> + <div class="card my-5"> + <h2 class="card-header" translate="admin.dashboard.past"></h2> + <ul class="list-group list-group-flush"> + <li *ngFor="let ev of expiredEvents$ | async" class="list-group-item"> + <a [routerLink]="['./', ev.id]" + >{{ ev.displayName }} {{ ev.formattedBegin }} + {{ ev.formattedEnd }}</a + > + </li> + </ul> + </div> + </ng-container> +</div> diff --git a/frontend/projects/admin/src/app/dashboard/dashboard.component.ts b/frontend/projects/admin/src/app/dashboard/dashboard.component.ts new file mode 100644 index 0000000000..a7dcfd0bdf --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/dashboard.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { map, mergeMap, Observable, of } from 'rxjs'; +import { EventInfo } from '../model/event'; +import { EventService } from '../shared/event.service'; +import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ExportDateSelectorComponent } from './export-date-selector/export-date-selector.component'; + +@Component({ + templateUrl: './dashboard.component.html', +}) +export class DashboardComponent implements OnInit { + public organizationId$: Observable<string | null> = of(); + public activeEvents$: Observable<EventInfo[]> = of(); + public expiredEvents$: Observable<EventInfo[]> = of(); + public activeFilter: boolean = true; + public inactiveFilter: boolean = false; + + constructor( + route: ActivatedRoute, + private readonly eventService: EventService, + private readonly modalService: NgbModal + ) { + this.organizationId$ = route.paramMap.pipe( + map((pm) => pm.get('organizationId')) + ); + } + + public ngOnInit(): void { + this.activeEvents$ = this.loadActiveEvents(); // default + } + + private loadActiveEvents() { + return this.organizationId$.pipe( + mergeMap((orgId) => + orgId != null ? this.eventService.getActiveEvents(orgId) : [] + ) + ); + } + + private loadInactiveEvents() { + return this.organizationId$.pipe( + mergeMap((orgId) => + orgId != null ? this.eventService.getExpiredEvents(orgId) : [] + ) + ); + } + + public toggleActiveFilter(toggle: boolean): void { + this.activeFilter = toggle; + if (toggle) { + this.activeEvents$ = this.loadActiveEvents(); + } else { + this.activeEvents$ = of(); + } + } + + public toggleInactiveFilter(toggle: boolean): void { + this.inactiveFilter = toggle; + if (toggle) { + this.expiredEvents$ = this.loadInactiveEvents(); + } else { + this.expiredEvents$ = of(); + } + } + public openExportDateSelector(): void { + const modalRef = this.modalService.open(ExportDateSelectorComponent, { + size: 'lg', + }); + } +} diff --git a/frontend/projects/admin/src/app/dashboard/dashboard.module.ts b/frontend/projects/admin/src/app/dashboard/dashboard.module.ts new file mode 100644 index 0000000000..bd57c89813 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/dashboard.module.ts @@ -0,0 +1,52 @@ +import { NgModule } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DashboardComponent } from './dashboard.component'; +import { RouterModule } from '@angular/router'; +import { OrganizationService } from '../shared/organization.service'; +import { CommonModule } from '@angular/common'; +import { EventService } from '../shared/event.service'; +import { OrganizationConfigurationComponent } from './organization-configuration/organization-configuration.component'; +import { provideSvgIconsConfig, SvgIconComponent } from '@ngneat/svg-icon'; +import { TranslateModule } from '@ngx-translate/core'; +import { ICON_CONFIG } from '../shared/icons'; +import { SubscriptionsComponent } from './subscriptions/subscriptions.component'; +import { OrganizationInfoComponent } from './organization-info/organization-info.component'; +import { GroupsComponent } from './groups/groups.component'; +import { FilterButtonComponent } from '../shared/filter-button/filter-button.component'; +import { EventItemComponent } from './event-item/event-item.component'; +import { ExportDateSelectorComponent } from './export-date-selector/export-date-selector.component'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + TranslateModule.forChild(), + CommonModule, + NgbModule, + RouterModule.forChild([ + { path: 'event', component: DashboardComponent }, + { path: 'configuration', component: OrganizationConfigurationComponent }, + { path: 'subscriptions', component: SubscriptionsComponent }, + { path: 'organization-info', component: OrganizationInfoComponent }, + { path: 'groups', component: GroupsComponent }, + ]), + SvgIconComponent, + FilterButtonComponent, + ReactiveFormsModule, + FormsModule, + ], + declarations: [ + DashboardComponent, + OrganizationConfigurationComponent, + SubscriptionsComponent, + OrganizationInfoComponent, + GroupsComponent, + EventItemComponent, + ExportDateSelectorComponent, + ], + providers: [ + OrganizationService, + EventService, + provideSvgIconsConfig(ICON_CONFIG), + ], +}) +export class DashboardModule {} diff --git a/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.html b/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.html new file mode 100644 index 0000000000..0ffb9fd7b1 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.html @@ -0,0 +1,52 @@ +<div class="row" *ngIf="event"> + <div class="col-2"> + <img + src="/file/{{ event.fileBlobId }}" + alt="" + class="img-fluid" + *ngIf="showImage" + /> + </div> + <div class="col-4"> + <h4 class="text-overflow" [title]="event.displayName"> + <a [routerLink]="['./', event.id]">{{ event.displayName }}</a> + </h4> + {{ event.formattedBegin }} / {{ event.formattedEnd }} + </div> + + <div class="col-6"> + <div class="d-flex justify-content-end flex-wrap"> + <a + class="btn btn-primary btn-sm mx-1" + *ngIf="!event.expired && event.visibleForCurrentUser" + [routerLink]="['./', event.id]" + > + <svg-icon key="ticket" size="md"></svg-icon> + {{ event.soldTickets + event.checkedInTickets }} / + {{ event.availableSeats }} + </a> + + <a + class="btn btn-primary btn-sm hidden-xs hidden-sm mx-1" + *ngIf="event.visibleForCurrentUser" + > + <svg-icon key="settings" size="md"></svg-icon>Settings + </a> + <a class="btn btn-primary btn-sm mx-1"> + <svg-icon key="check" size="md"></svg-icon> Check-In + </a> + <a + class="btn btn-warning btn-sm" + *ngIf=" + event.visibleForCurrentUser && + event.allowedPaymentProxies.includes('OFFLINE') + " + > + <svg-icon key="payments" size="md"></svg-icon> Pending payments<span + class="badge bg-secondary" + >{{ event.shortName }}</span + > + </a> + </div> + </div> +</div> diff --git a/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.scss b/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.scss new file mode 100644 index 0000000000..b8cd06c537 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.scss @@ -0,0 +1,5 @@ +.text-overflow { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.ts b/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.ts new file mode 100644 index 0000000000..0587ea356d --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/event-item/event-item.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { EventInfo } from '../../model/event'; + +@Component({ + selector: 'app-event-item', + templateUrl: './event-item.component.html', + styleUrls: ['./event-item.component.scss'], +}) +export class EventItemComponent implements OnInit { + @Input() + public event: EventInfo | undefined; + + @Input() + showImage: boolean = true; + + constructor() {} + + ngOnInit(): void {} +} diff --git a/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.html b/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.html new file mode 100644 index 0000000000..1dafe888b4 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.html @@ -0,0 +1,78 @@ +<div class="modal-header"> + <h2>Export Reservations</h2> + <button + type="button" + class="btn-close" + aria-label="Close" + (click)="activeModal.dismiss('Close click')" + ></button> +</div> +<div class="modal-body"> + <div class="row"> + <p>Reservations can be exported for a given period of time.</p> + <p>The export will produce an "Excel" file with one sheet per Event</p> + <hr /> + <h4>Reservation date</h4> + + <div class="row"> + <form + [formGroup]="dateForm" + class="form-group d-flex justify-content-between" + > + <div class="col-6 mb-3 mx-2"> + <label for="fromDate" class="form-label">From</label> + <input + type="date" + id="fromDate" + class="form-control" + formControlName="fromDate" + [class.is-invalid]=" + fromDate?.invalid && (fromDate?.dirty || fromDate?.touched) + " + /> + </div> + <div class="col-6"> + <label for="toDate" class="form-label">To</label> + <input + type="date" + id="toDate" + class="form-control" + formControlName="toDate" + [class.is-invalid]=" + toDate?.invalid && (toDate?.dirty || toDate?.touched) + " + /> + </div> + </form> + </div> + </div> + <div class="modal-footer d-flex justify-content-between"> + <div> + <button + class="btn btn-lg btn-outline-secondary" + (click)="activeModal.dismiss('Close click')" + > + Cancel + </button> + </div> + <div> + <a + [href]=" + '/admin/api/export/reservations?from=' + + dateForm.value.fromDate + + '&to=' + + dateForm.value.toDate + " + class="btn btn-lg btn-primary" + target="_blank" + rel="noopener" + *ngIf=" + dateForm.value.fromDate !== null && + dateForm.value.toDate !== null && + dateForm.value.fromDate <= dateForm.value.toDate + " + >Download</a + > + </div> + </div> +</div> diff --git a/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.scss b/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.ts b/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.ts new file mode 100644 index 0000000000..f075ece92b --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/export-date-selector/export-date-selector.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; +import { FormGroup } from '@angular/forms'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-export-date-selector', + templateUrl: './export-date-selector.component.html', + styleUrls: ['./export-date-selector.component.scss'], +}) +export class ExportDateSelectorComponent implements OnInit { + public dateForm: FormGroup; + constructor( + private readonly modalService: NgbModal, + public activeModal: NgbActiveModal, + formBuilder: FormBuilder + ) { + this.dateForm = formBuilder.group({ + fromDate: [null, Validators.required], + toDate: [null, Validators.required], + }); + } + + get fromDate() { + return this.dateForm.get('fromDate'); + } + + get toDate() { + return this.dateForm.get('toDate'); + } + + ngOnInit(): void {} +} diff --git a/frontend/projects/admin/src/app/dashboard/groups/groups.component.html b/frontend/projects/admin/src/app/dashboard/groups/groups.component.html new file mode 100644 index 0000000000..41ad0f66d7 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/groups/groups.component.html @@ -0,0 +1 @@ +<p>groups works!</p> diff --git a/frontend/projects/admin/src/app/dashboard/groups/groups.component.scss b/frontend/projects/admin/src/app/dashboard/groups/groups.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/dashboard/groups/groups.component.ts b/frontend/projects/admin/src/app/dashboard/groups/groups.component.ts new file mode 100644 index 0000000000..54f33cf9e1 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/groups/groups.component.ts @@ -0,0 +1,15 @@ +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-groups', + templateUrl: './groups.component.html', + styleUrls: ['./groups.component.scss'] +}) +export class GroupsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.html b/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.html new file mode 100644 index 0000000000..b0dc87782d --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.html @@ -0,0 +1 @@ +<p>organization-configuration works!</p> diff --git a/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.scss b/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.ts b/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.ts new file mode 100644 index 0000000000..067d201870 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/organization-configuration/organization-configuration.component.ts @@ -0,0 +1,15 @@ +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-organization-configuration', + templateUrl: './organization-configuration.component.html', + styleUrls: ['./organization-configuration.component.scss'] +}) +export class OrganizationConfigurationComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.html b/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.html new file mode 100644 index 0000000000..5cf34fe66b --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.html @@ -0,0 +1 @@ +<p>organization-info works!</p> diff --git a/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.scss b/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.ts b/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.ts new file mode 100644 index 0000000000..6cf39b8836 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/organization-info/organization-info.component.ts @@ -0,0 +1,15 @@ +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-organization-info', + templateUrl: './organization-info.component.html', + styleUrls: ['./organization-info.component.scss'] +}) +export class OrganizationInfoComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.html b/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.html new file mode 100644 index 0000000000..a0996687a0 --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.html @@ -0,0 +1 @@ +<p>subscriptions works!</p> diff --git a/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.scss b/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.ts b/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.ts new file mode 100644 index 0000000000..d9b689c0ee --- /dev/null +++ b/frontend/projects/admin/src/app/dashboard/subscriptions/subscriptions.component.ts @@ -0,0 +1,15 @@ +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-subscriptions', + templateUrl: './subscriptions.component.html', + styleUrls: ['./subscriptions.component.scss'] +}) +export class SubscriptionsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/event/email-log/email-log.component.html b/frontend/projects/admin/src/app/event/email-log/email-log.component.html new file mode 100644 index 0000000000..8f60b113f6 --- /dev/null +++ b/frontend/projects/admin/src/app/event/email-log/email-log.component.html @@ -0,0 +1 @@ +<p>email-log works!</p> diff --git a/frontend/projects/admin/src/app/event/email-log/email-log.component.scss b/frontend/projects/admin/src/app/event/email-log/email-log.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/event/email-log/email-log.component.ts b/frontend/projects/admin/src/app/event/email-log/email-log.component.ts new file mode 100644 index 0000000000..3a55c133d0 --- /dev/null +++ b/frontend/projects/admin/src/app/event/email-log/email-log.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-email-log', + templateUrl: './email-log.component.html', + styleUrls: ['./email-log.component.scss'] +}) +export class EmailLogComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.html b/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.html new file mode 100644 index 0000000000..a1d4c85d4e --- /dev/null +++ b/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.html @@ -0,0 +1,812 @@ +<app-section-dashboard [hasMenu]="true"> + <div menu> + <app-event-menu></app-event-menu> + </div> + <div + *ngIf="event$ | async as event" + class="d-flex flex-row align-items-center" + > + <a class="btn" href=""><svg-icon key="edit"></svg-icon></a> + <h2 class="my-4">{{ event.displayName }}</h2> + </div> + <hr /> + <div *ngIf="eventOrganizationInfo$ | async as eventOrganizationInfo"> + <div *ngIf="eventOrganizationInfo.event.displayStatistics" class="row"> + <div class="col-6 col-md-3"> + <div class="card flex-fill mx-3"> + <div + class="card-header text-center" + translate="admin.event-dashboard.total-tickets-confirmed" + ></div> + <div class="card-body d-flex justify-content-between"> + <div> + <svg-icon key="ticket" width="70px" height="70px"></svg-icon> + </div> + + <div class="fs-1"> + {{ + eventOrganizationInfo.event.soldTickets + + eventOrganizationInfo.event.checkedInTickets + }} + </div> + </div> + </div> + </div> + <div class="col-6 col-md-3"> + <div class="card flex-fill mx-3"> + <div + class="card-header text-center" + translate="admin.event-dashboard.tickets-pending" + ></div> + <div class="card-body d-flex justify-content-between"> + <div> + <svg-icon + key="hourglass-top" + width="70px" + height="70px" + ></svg-icon> + </div> + <div class="fs-1"> + {{ eventOrganizationInfo.event.pendingTickets }} + </div> + </div> + </div> + </div> + <div class="col-6 col-md-6"> + <div + class="card flex-fill mx-3" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + > + <div + class="card-header text-center" + translate="admin.event-dashboard.gross-income" + ></div> + <div class="card-body d-flex justify-content-between"> + <div> + <svg-icon key="piggy-bank" width="70px" height="70px"></svg-icon> + </div> + <div class="fs-1"> + {{ eventOrganizationInfo.event.grossIncome }} + {{ eventOrganizationInfo.event.currency }} + </div> + </div> + </div> + </div> + </div> + <div class="row my-4"> + <div + style="max-height: 250px" + class="col-6 d-flex justify-content-center align-items-center" + > + <canvas + width="700px" + height="250px" + baseChart + [type]="'line'" + [data]="lineChartData" + [options]="lineChartOptions" + [legend]="lineChartLegend" + > + </canvas> + </div> + + <div + style="max-height: 250px" + class="col-6 d-flex justify-content-center align-items-center" + > + <canvas + baseChart + height="250px" + [type]="'pie'" + [data]="pieChartData" + [options]="pieChartOptions" + [legend]="true" + > + </canvas> + </div> + </div> + </div> + + <div class="card" *ngIf="event$ | async as event"> + <div + class="card-header" + translate="admin.event-dashboard.logistics-info-and-description" + ></div> + <div + class="card-body" + *ngIf="eventOrganizationInfo$ | async as eventOrganizationInfo" + > + <div class="row"> + <div class="col-2"> + <img + src="/file/{{ eventOrganizationInfo.event.fileBlobId }}" + alt="" + class="img-fluid" + /> + </div> + <div class="col-6"> + <dl class="row"> + <dt + class="col-sm-3" + translate="admin.event-dashboard.public-url" + ></dt> + <dd class="col-sm-9"> + <a + [href]=" + (instanceSetting$ | async)?.baseUrl + + '/event/' + + eventOrganizationInfo.event.shortName + " + target="_blank" + rel="noopener" + >{{ + (instanceSetting$ | async)?.baseUrl + + "/event/" + + eventOrganizationInfo.event.shortName + }}</a + > + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.organized-by" + ></dt> + <dd class="col-sm-9"> + {{ eventOrganizationInfo.organization.name }} <{{ + eventOrganizationInfo.organization.email + }}> + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.website-url" + ></dt> + <dd class="col-sm-9"> + <a + href="{{ eventOrganizationInfo.event.websiteUrl }}" + target="_blank" + rel="noopener" + >{{ eventOrganizationInfo.event.websiteUrl }}</a + > + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.t-and-c-url" + ></dt> + <dd class="col-sm-9"> + <a + href="{{ eventOrganizationInfo.event.termsAndConditionsUrl }}" + target="_blank" + rel="noopener" + >{{ eventOrganizationInfo.event.termsAndConditionsUrl }}</a + > + </dd> + <dt + *ngIf="eventOrganizationInfo.event.privacyPolicyUrl" + class="col-sm-3" + translate="admin.event-dashboard.privacy-policy-url" + ></dt> + <dd + *ngIf="eventOrganizationInfo.event.privacyPolicyUrl" + class="col-sm-9" + > + <a + href="{{ eventOrganizationInfo.event.privacyPolicyUrl }}" + target="_blank" + rel="noopener" + >{{ eventOrganizationInfo.event.privacyPolicyUrl }}</a + > + </dd> + <dt + *ngIf="!eventOrganizationInfo.event.online" + class="col-sm-3" + translate="admin.event-dashboard.location" + ></dt> + <dd *ngIf="!eventOrganizationInfo.event.online" class="col-sm-9"> + <a + href="{{ eventOrganizationInfo.event.location }}" + target="_blank" + rel="noopener" + >{{ eventOrganizationInfo.event.location }}</a + > + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.languages" + ></dt> + <dd class="col-sm-9"> + <ul class="breadcrumb language-breadcrumb mb-0"> + <li + class="breadcrumb-item" + *ngFor=" + let language of eventOrganizationInfo.event.contentLanguages + " + > + <span>{{ language.displayLanguage | titlecase }}</span> + </li> + </ul> + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.start-date-time" + ></dt> + <dd class="col-sm-9"> + {{ eventOrganizationInfo.event.formattedBegin | date }} + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.end-date-time" + ></dt> + <dd class="col-sm-9"> + {{ eventOrganizationInfo.event.formattedEnd | date }} + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.time-zone" + ></dt> + <dd class="col-sm-9">{{ eventOrganizationInfo.event.timeZone }}</dd> + </dl> + </div> + <div class="col-4"> + <ul ngbNav #nav="ngbNav" class="nav-tabs"> + <li + *ngFor=" + let language of eventOrganizationInfo.event.description + | keyvalue; + let i = index + " + [ngbNavItem]="language.key" + > + <a ngbNavLink>{{ language.key }}</a> + <ng-template ngbNavContent> + <pre class="pt-3">{{ language.value }}</pre> + </ng-template> + </li> + </ul> + + <div [ngbNavOutlet]="nav" class="mt-2"></div> + </div> + </div> + <hr /> + <div class="d-flex flex-row-reverse"> + <button class="btn btn-lg btn-primary" translate="common.edit"></button> + </div> + </div> + </div> + <div + class="card mt-5" + *ngIf="eventOrganizationInfo$ | async as eventOrganizationInfo" + > + <div + class="card-header" + translate="admin.event-dashboard.seats-and-payment-info" + ></div> + <div class="card-body"> + <div class="row"> + <div class="col-xs-12 col-lg-6"> + <dl class="row"> + <dt + class="col-sm-3" + translate="admin.event-dashboard.tickets-price" + ></dt> + <dd + class="col-sm-9" + translate="admin.event-dashboard.free-of-charge" + > + <span *ngIf="eventOrganizationInfo.event.freeOfCharge"></span> + <span *ngIf="!eventOrganizationInfo.event.freeOfCharge">{{ + eventOrganizationInfo.event.regularPrice + | currency : eventOrganizationInfo.event.currency || "" + }}</span> + </dd> + <dt + class="col-sm-3" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + translate="admin.event-dashboard.payment-methods" + ></dt> + <dd + class="col-sm-9" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + > + {{ + eventOrganizationInfo.event.allowedPaymentProxies + | translatePaymentProxies + }} + <!-- <div + class="text-warning" + data-ng-if="(allowedPaymentProxies | paymentMethodFilter : true : event.currency).length > 0" + > + <i class="fa fa-info-circle"></i> The following Methods are not + available: + <span + data-ng-repeat="p in allowedPaymentProxies | paymentMethodFilter : true : event.currency" + >{{ p.id }}<span data-ng-if="!$last">, </span></span + >. Please check that the provider support the selected currency or + the + <a + data-ui-sref="configuration.organization({organizationId: event.organizationId})" + >configuration</a + >. + </div> --> + </dd> + <dt + class="col-sm-3" + translate="admin.event-dashboard.max-tickets" + ></dt> + <dd class="col-sm-9"> + {{ eventOrganizationInfo.event.availableSeats }} + </dd> + <dt + class="col-sm-3" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + translate="admin.event-dashboard.vat" + ></dt> + <dd + class="col-sm-9" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + > + {{ eventOrganizationInfo.event.vatPercentage | currency : "" }} + </dd> + <dt + class="col-sm-3" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + translate="admin.event-dashboard.final-price" + ></dt> + <dd + class="col-sm-9" + *ngIf="!eventOrganizationInfo.event.freeOfCharge" + > + {{ + eventOrganizationInfo.event.finalPrice + | currency : eventOrganizationInfo.event.currency || "" + }} + </dd> + </dl> + </div> + </div> + <div class="row"> + <div class="col-12"> + <hr /> + <div class="d-flex flex-row-reverse"> + <button + class="btn btn-lg btn-primary" + translate="common.edit" + ></button> + </div> + </div> + </div> + </div> + </div> + <h3 class="mt-5"> + <svg-icon key="category"></svg-icon> + <span translate="admin.event-dashboard.categories"></span> + </h3> + <p class="text-muted" translate="admin.event-dashboard.categories-legend"></p> + <div class="py-2"> + <button + type="button" + class="btn btn-success d-flex align-items-center" + [routerLink]="[]" + translate="admin.common.add-new" + > + <svg-icon key="add" class="d-flex align-items-center"></svg-icon> + <span translate="admin.common.add-new"></span> + </button> + </div> + <hr /> + <div *ngIf="eventOrganizationInfo$ | async as eventOrganizationInfo"> + <div + class="btn-toolbar mb-3" + role="toolbar" + aria-label="Toolbar with input groups" + > + <div class="btn-group me-2" role="group" aria-label="First group"> + <input + type="checkbox" + class="btn-check" + id="checkBoxActive" + autocomplete="off" + [(ngModel)]="categoryFilter.active" + /> + <label + class="btn btn-outline-secondary" + for="checkBoxActive" + translate="admin.event-dashboard.active" + ><span class="badge rounded-pill bg-light text-dark mx-2">{{ + countActive(eventOrganizationInfo.event.ticketCategories) + }}</span></label + > + + <input + type="checkbox" + class="btn-check" + id="checkBoxExpired" + autocomplete="off" + [(ngModel)]="categoryFilter.expired" + /> + <label + class="btn btn-outline-secondary" + for="checkBoxExpired" + translate="admin.event-dashboard.expired" + ><span class="badge rounded-pill bg-light text-dark mx-2">{{ + countExpired(eventOrganizationInfo.event.ticketCategories) + }}</span></label + > + </div> + <div class="input-group"> + <span class="input-group-text" id="btnGroupAddon"> + <svg-icon key="search"></svg-icon> + </span> + <input + type="text" + class="form-control" + placeholder="Search category" + aria-label="Input group example" + aria-describedby="btnGroupAddon" + [(ngModel)]="categoryFilter.search" + /> + </div> + </div> + <div + [ngSwitch]=" + ( + eventOrganizationInfo.event.ticketCategories + | uiCategoryBuilder + | showSelectedCategories : categoryFilter + ).length + " + > + <div + class="alert alert-warning" + translate="admin.event-dashboard.alert-warning" + *ngSwitchCase="0" + ></div> + <div *ngSwitchDefault> + <div + class="card mb-4" + *ngFor=" + let ticketCategory of eventOrganizationInfo.event.ticketCategories + | uiCategoryBuilder + | showSelectedCategories : categoryFilter + " + [ngClass]="{ + 'alert alert-warning': ticketCategory.containingOrphans, + 'alert alert-danger': ticketCategory.containingStuckTickets + }" + id="ticket-category-{{ ticketCategory.id }}" + > + <div class="card-header"> + <div class="card-title"> + <div class="row align-items-center"> + <div + class="col-xs-12 col-md-3" + [ngClass]="{ 'text-danger': ticketCategory.displayWarning }" + > + <div + class="category-name" + [ngClass]="{ 'category-active': !ticketCategory.expired }" + > + <svg-icon + key="lock" + *ngIf=" + !ticketCategory.displayWarning && + ticketCategory.accessRestricted + " + ></svg-icon> + <svg-icon + key="lock-open" + *ngIf=" + !ticketCategory.displayWarning && + !ticketCategory.accessRestricted + " + ></svg-icon> + + {{ ticketCategory.name }} + <small + translate="admin.event-dashboard.online" + class="badge bg-success" + style="margin-left: 10px" + *ngIf=" + eventOrganizationInfo.event.format === 'HYBRID' && + ticketCategory.ticketAccessType === 'ONLINE' + " + ></small> + </div> + </div> + <div class="hidden-xs hidden-sm col-md-3"> + <div class="progress"> + <div + *ngIf="ticketCategory.checkedInTickets != 0" + class="progress-bar bg-success" + role="progressbar" + [style]=" + 'width: ' + + (ticketCategory.checkedInTickets / + getActualCapacity( + ticketCategory, + eventOrganizationInfo.event + )) * + 100 + + '%' + " + > + {{ ticketCategory.checkedInTickets }} + </div> + <div + *ngIf="ticketCategory.soldTickets != 0" + class="progress-bar bg-warning" + role="progressbar" + [style]=" + 'width: ' + + (ticketCategory.soldTickets / + getActualCapacity( + ticketCategory, + eventOrganizationInfo.event + )) * + 100 + + '%' + " + > + {{ ticketCategory.soldTickets }} + </div> + <div + *ngIf="ticketCategory.pendingTickets != 0" + class="progress-bar bg-info" + role="progressbar" + [style]=" + 'width: ' + + (ticketCategory.pendingTickets / + getActualCapacity( + ticketCategory, + eventOrganizationInfo.event + )) * + 100 + + '%' + " + > + {{ ticketCategory.pendingTickets }} + </div> + <div + *ngIf="ticketCategory.notSoldTickets != 0" + class="progress-bar bg-info" + role="progressbar" + [style]=" + 'width: ' + + (ticketCategory.notSoldTickets / + getActualCapacity( + ticketCategory, + eventOrganizationInfo.event + )) * + 100 + + '%' + " + > + {{ ticketCategory.notSoldTickets }} + </div> + <div + *ngIf="!ticketCategory.bounded" + type="default" + class="progress-bar" + role="progressbar" + [style]=" + 'width: ' + + (eventOrganizationInfo.event.dynamicAllocation / + getActualCapacity( + ticketCategory, + eventOrganizationInfo.event + )) * + 100 + + '%' + " + > + {{ eventOrganizationInfo.event.dynamicAllocation }} + </div> + </div> + </div> + + <div class="col-xs-12 col-md-6"> + <div class="d-flex justify-content-end"> + <a + class="btn btn-outline-secondary btn-sm" + href="events.single.ticketsList({eventName: event.shortName, categoryId: ticketCategory.id})" + > + <svg-icon key="ticket" size="md"></svg-icon> + <span translate="admin.event-dashboard.tickets"></span> + </a> + <a + class="btn btn-outline-secondary btn-sm" + (click)="toggleTokenViewCollapse(ticketCategory)" + *ngIf="ticketCategory.accessRestricted" + > + <svg-icon key="qr-code" size="md"></svg-icon> + <span translate="admin.event-dashboard.tokens"></span> + </a> + <a + class="btn btn-outline-secondary btn-sm" + href="events.single.sendInvitations({eventName: event.shortName, categoryId: ticketCategory.id})" + *ngIf=" + ticketCategory.accessRestricted && + !ticketCategory.expired + " + > + <svg-icon key="outgoing-mail" size="md"></svg-icon> + <span + translate="admin.event-dashboard.send-invitations" + ></span> + </a> + <a + class="btn btn-sm btn-outline-secondary" + (click)=" + openConfiguration( + eventOrganizationInfo.event, + ticketCategory + ) + " + ><svg-icon key="settings" size="md"></svg-icon> + <span translate="admin.event-dashboard.options"></span + ></a> + <a + class="btn btn-sm btn-danger" + ng-if="canBeDeleted(event, ticketCategory)" + ng-click="deleteCategory(ticketCategory, event)" + ><svg-icon key="delete" size="md"></svg-icon> + <span translate="admin.common.delete"></span + ></a> + </div> + </div> + </div> + </div> + </div> + <div class="card-body"> + <div + *ngIf="ticketCategory.attendeesList" + class="alert alert-info text-center" + > + <span + translate="admin.event-dashboard.alert-message-limited-group" + > + <a + href="groups.edit({orgId: event.organizationId, groupId: ticketCategory.attendeesList.groupId})" + target="_blank" + >{{ ticketCategory.attendeesList }}</a + > + <button + type="button" + class="btn btn-outline-secondary btn-sm" + ng-click="openConfiguration(event, ticketCategory)" + translate="admin.event-dashboard.configure" + > + <svg-icon key="settings"></svg-icon></button + ></span> + </div> + <div + class="row form-inline text-danger" + *ngIf="ticketCategory.containingOrphans" + > + <div class="col-xs-12" data-ng-form="moveTickets"> + <svg-icon key="warning"></svg-icon> + <span translate="admin.event-dashboard.message-orphans"></span> + <select + class="form-control input-sm" + data-ng-model="targetCategoryId" + required + data-ng-options="category.id as category.name for category in validCategories" + ></select> + <button + class="btn btn-sm btn-warning" + data-ng-click="moveOrphans(ticketCategory, targetCategoryId, event.id)" + translate="admin.event-dashboard.apply" + ></button> + <button + *ngIf=" + eventOrganizationInfo.event.containingUnboundedCategories + " + class="btn btn-sm btn-warning" + (click)=" + unbindTickets( + eventOrganizationInfo.event.shortName, + ticketCategory + ) + " + translate="admin.event-dashboard.assign-to-dynamic-categories" + ></button> + </div> + </div> + <div + class="row text-warning" + *ngIf="ticketCategory.containingStuckTickets" + > + <div class="col-xs-12"> + <svg-icon key="warning"></svg-icon> + <span + translate="admin.event-dashboard.alert-message-category-tickets-unknown-state" + ></span> + <a + translate="admin.event-dashboard.alert-message-category-tickets-unknown-state-link" + ui-sref="events.single.ticketsList({eventName: event.shortName, categoryId: ticketCategory.id})" + ></a + >. + </div> + </div> + + <app-ticket-category-detail></app-ticket-category-detail> + <div + *ngIf="ticketCategory.accessRestricted" + class="wMarginBottom" + data-uib-collapse="isTokenViewCollapsed(ticketCategory)" + > + <!-- <hr /> --> + <!-- <button + class="btn btn-outline-dark" + aria-label="Close" + data-ng-click="toggleTokenViewCollapse(ticketCategory)" + > + <svg-icon key="close"></svg-icon> + </button> --> + <!-- <h4>Tokens</h4> + <div *ngIf="containsValidTokens(ticketCategory.tokenStatus)"> + <div + *ngFor="(status, tokens) in groupTokensByStatus(ticketCategory.tokenStatus)" + > + <h5><strong>{{status}}</strong></h5> + <ul class="list-inline"> + <li data-ng-repeat="token in ::tokens"> + <a + ng-if="token.status === 'TAKEN'" + data-ui-sref="events.single.reservationsList({eventName: event.shortName, search: token.code})" + title="Find reservation" + >{{::token.code}}</a + > + <span data-ng-if="token.status !== 'TAKEN'">{{::token.code}}</span> + </li> + </ul> + </div> + </div> --> + + <div + class="alert alert-info" + *ngIf="!containsValidTokens(ticketCategory.tokenStatus)" + > + <div class="row"> + <div class="col-xs-1"> + <svg-icon key="info"></svg-icon> + </div> + <div + class="col-xs-11" + translate="admin.event-dashboard.message-token-generation" + ></div> + </div> + </div> + </div> + <div + *ngIf=" + eventOrganizationInfo.event.format === 'IN_PERSON' || + ticketCategory.ticketAccessType === 'IN_PERSON' + " + > + <hr /> + <div class="row"> + <div class="col-12"> + <div class="d-flex justify-content-end"> + <button + class="btn btn-lg btn-primary btn-lg" + translate="admin.event-dashboard.edit-category" + ></button> + </div> + </div> + </div> + </div> + <div + *ngIf=" + eventOrganizationInfo.event.format === 'ONLINE' || + ticketCategory.ticketAccessType === 'ONLINE' + " + > + <!-- <metadata-viewer event="event" parent-id="ticketCategory.id" metadata="ticketCategory.metadata" available-languages="event.contentLanguages" level="'category'"> + <button type="button" class="btn btn-warning btn-block" data-ng-click="editCategory(ticketCategory, event)">Edit category</button> + </metadata-viewer> --> + </div> + </div> + </div> + </div> + </div> + </div> +</app-section-dashboard> diff --git a/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.scss b/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.scss @@ -0,0 +1 @@ + diff --git a/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.ts b/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.ts new file mode 100644 index 0000000000..2fd8e15211 --- /dev/null +++ b/frontend/projects/admin/src/app/event/event-dashboard/event-dashboard.component.ts @@ -0,0 +1,211 @@ +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {ChartConfiguration, ChartData, ChartOptions} from 'chart.js'; +import {map, Observable, of, switchMap} from 'rxjs'; +import {Event, EventInfo, EventOrganizationInfo, EventTicketsStatistics,} from '../../model/event'; +import {EventService} from '../../shared/event.service'; +import {formatDate} from '@angular/common'; +import {ConfigurationService} from '../../shared/configuration.service'; +import {InstanceSetting} from '../../model/instance-settings'; +import {TicketCategory, TicketCategoryFilter, TicketTokenStatus, UiTicketCategory} from '../../model/ticket-category'; + +@Component({ + selector: 'app-event-dashboard', + templateUrl: './event-dashboard.component.html', + styleUrls: ['./event-dashboard.component.scss'], +}) +export class EventDashboardComponent implements OnInit { + public eventId$: Observable<string | null> = of(); + public event$: Observable<Event | null> = of(); + public eventOrganizationInfo$: Observable<EventOrganizationInfo | null> = + of(); + public eventTicketsStatistics$: Observable<EventTicketsStatistics | null> = + of(); + public instanceSetting$: Observable<InstanceSetting> = of(); + + public pieChartOptions: ChartOptions<'pie'> = { + responsive: true, + + plugins: { + legend: { + display: true, + position: 'right', + }, + }, + }; + public pieChartData: ChartData<'pie', number[], string | string[]> = { + labels: [], + datasets: [ + { + data: [], + backgroundColor: [], + }, + ], + }; + + public lineChartData: ChartConfiguration<'line'>['data'] = { + labels: [], + datasets: [], + }; + public lineChartOptions: ChartOptions<'line'> = { + responsive: true, + maintainAspectRatio: false, + }; + public lineChartLegend = true; + + public categoryFilter: TicketCategoryFilter = { + active: true, + expired: false, + search: '', + }; + + constructor( + private readonly eventService: EventService, + private readonly route: ActivatedRoute, + private readonly configurationService: ConfigurationService + ) { + this.eventId$ = route.paramMap.pipe(map((pm) => pm.get('eventId'))); + this.instanceSetting$ = this.configurationService.loadInstanceSetting(); + + this.event$ = this.eventId$.pipe( + switchMap((eventId) => + eventId != null ? eventService.getEvent(eventId) : of() + ) + ); + + this.eventOrganizationInfo$ = this.event$.pipe( + switchMap((event) => + event != null ? eventService.getEventByShortName(event.shortName) : of() + ) + ); + + this.eventTicketsStatistics$ = this.event$.pipe( + switchMap((event) => + event != null + ? eventService.getTicketsStatistics(event.shortName) + : of() + ) + ); + + this.eventOrganizationInfo$.subscribe((eventOrganizationInfo) => { + const event = eventOrganizationInfo?.event; + if (!event) { + return; + } + this.loadPieData(event); + }); + + this.eventTicketsStatistics$.subscribe((eventTicketsStatistics) => { + if (!eventTicketsStatistics) { + return; + } + this.loadLineData(eventTicketsStatistics); + }); + } + + loadLineData(eventTicketsStatistics: EventTicketsStatistics) { + this.lineChartData = { + labels: eventTicketsStatistics.sold.map((item) => + formatDate(item.date, 'mediumDate', 'en') + ), + datasets: [ + { + data: eventTicketsStatistics.sold.map((item) => item.count), + label: 'Created Reservations', + borderColor: 'green', + backgroundColor: 'green', + }, + { + data: eventTicketsStatistics.reserved.map((item) => item.count), + label: 'Confirmed Reservations', + borderColor: 'orange', + backgroundColor: 'orange', + }, + ], + }; + } + + loadPieData(event: EventInfo) { + const pieData = [ + { + value: event.checkedInTickets, + name: 'Checked in', + backgroundColor: '#5cb85c', + meta: 'Checked in (' + event.checkedInTickets + ')', + }, + { + value: event.soldTickets, + name: 'Sold', + backgroundColor: '#f0ad4e', + meta: 'Sold (' + event.soldTickets + ')', + }, + { + value: event.pendingTickets + event.releasedTickets, + name: 'Pending', + backgroundColor: '#7670b7', + meta: + 'Pending (' + (event.pendingTickets + event.releasedTickets) + ')', + }, + { + value: event.notSoldTickets, + name: 'Reserved for categories', + backgroundColor: '#00C4FF', + meta: 'Reserved for categories (' + event.notSoldTickets + ')', + }, + { + value: event.notAllocatedTickets, + name: 'Not yet allocated', + backgroundColor: '#d9534f', + meta: 'Not yet allocated (' + event.notAllocatedTickets + ')', + }, + { + value: event.dynamicAllocation, + name: 'Available', + backgroundColor: '#428bca', + meta: 'Available (' + event.dynamicAllocation + ')', + }, + ].filter((item) => item.value > 0); + + this.pieChartData = { + labels: pieData.map((item) => item.name), + datasets: [ + { + data: pieData.map((item) => item.value), + backgroundColor: pieData.map((item) => item.backgroundColor), + }, + ], + }; + } + countExpired(ticketCategory: TicketCategory[]) { + return ticketCategory.filter((ticketCategory) => ticketCategory.expired) + .length; + } + countActive(ticketCategory: TicketCategory[]) { + return ticketCategory.filter((ticketCategory) => !ticketCategory.expired) + .length; + } + + getActualCapacity(ticketCategory: TicketCategory, event: EventInfo) { + return ticketCategory.bounded + ? ticketCategory.maxTickets + : event.dynamicAllocation + ticketCategory.soldTickets; + } + + toggleTokenViewCollapse(ticketCategory: UiTicketCategory) { + ticketCategory.tokenViewExpanded = !ticketCategory.tokenViewExpanded; + } + + openConfiguration(event: EventInfo, ticketCategory: TicketCategory) { + alert('TODO'); + } + + containsValidTokens(tokens: TicketTokenStatus[]) { + return tokens.every((token) => token.status !== 'WAITING'); + } + + unbindTickets(eventShortName: string, category: UiTicketCategory) { + this.eventService.unbindTickets(eventShortName, category); + } + + ngOnInit(): void {} +} diff --git a/frontend/projects/admin/src/app/event/event-menu/event-menu.component.html b/frontend/projects/admin/src/app/event/event-menu/event-menu.component.html new file mode 100644 index 0000000000..e068c0f869 --- /dev/null +++ b/frontend/projects/admin/src/app/event/event-menu/event-menu.component.html @@ -0,0 +1,132 @@ +<div class="container"> + <div class="d-flex flex-row container mt-3 d-sm-block"> + <div ngbDropdown class="d-inline-block me-1"> + <button + type="button" + class="btn btn-outline-primary" + id="dropdownBasic1" + ngbDropdownToggle + translate="admin.event.menu.actions" + ></button> + <div ngbDropdownMenu aria-labelledby="dropdownBasic1"> + <button + ngbDropdownItem + translate="admin.event.menu.create-reservation" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.import-attendees" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.edit-configuration" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.customize-templates" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.compose-message" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.hide-from-list" + ></button> + <button ngbDropdownItem translate="admin.event.menu.check-in"></button> + <button + ngbDropdownItem + translate="admin.event.menu.copy-this-event" + ></button> + <button ngbDropdownItem translate="admin.common.delete"></button> + </div> + </div> + <div ngbDropdown class="d-inline-block"> + <button + type="button" + class="btn btn-outline-primary" + id="dropdownBasic1" + ngbDropdownToggle + translate="admin.event.menu.download" + ></button> + <div ngbDropdownMenu aria-labelledby="dropdownBasic1"> + <button + ngbDropdownItem + translate="admin.event.menu.attendees-data" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.waiting-list" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.sponsors-scan" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.all-billing-documents-pdf" + ></button> + <button + ngbDropdownItem + translate="admin.event.menu.all-billing-excel" + ></button> + </div> + </div> + </div> + <div class="container"> + <h3 class="text-muted mt-3" translate="admin.event.menu.details"></h3> + <ul class="list-unstyled"> + <li> + <ul class="list-unstyled"> + <li> + <a class="text-decoration-none" href="" + ><svg-icon key="arrow-back"></svg-icon> + <span translate="admin.event.menu.back-to-event-detail"></span + ></a> + </li> + <li> + <a class="text-decoration-none" href=""> + <svg-icon key="description"></svg-icon> + <span + translate="admin.event.menu.attendees-data-to-collect" + ></span + ></a> + </li> + <li> + <a class="text-decoration-none" href="" + ><svg-icon key="percent"></svg-icon> + <span translate="admin.event.menu.promo-codes"></span + ></a> + </li> + <li> + <a class="text-decoration-none" href="" + ><svg-icon key="insights"></svg-icon> + <span translate="admin.event.menu.polls"></span + ></a> + </li> + <li> + <a class="text-decoration-none" href="" + ><svg-icon key="ticket-reserved"></svg-icon> + <span translate="admin.event.menu.reservations"></span + ></a> + </li> + <li> + <a class="text-decoration-none" routerLink="./email-log" + ><svg-icon key="email-sent"></svg-icon> + <span translate="admin.event.menu.email-log"></span + ></a> + </li> + </ul> + <h3 class="text-muted mt-3" translate="admin.event.menu.status"></h3> + <ul class="list-unstyled"> + <li> + <a class="text-decoration-none" href="" + ><svg-icon key="waiting-list"></svg-icon> + <span translate="admin.event.menu.waiting-list"></span + ></a> + </li> + </ul> + </li> + </ul> + </div> +</div> diff --git a/frontend/projects/admin/src/app/event/event-menu/event-menu.component.scss b/frontend/projects/admin/src/app/event/event-menu/event-menu.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/event/event-menu/event-menu.component.ts b/frontend/projects/admin/src/app/event/event-menu/event-menu.component.ts new file mode 100644 index 0000000000..e915ac0517 --- /dev/null +++ b/frontend/projects/admin/src/app/event/event-menu/event-menu.component.ts @@ -0,0 +1,15 @@ +import {Component, OnInit} from '@angular/core'; + +@Component({ + selector: 'app-event-menu', + templateUrl: './event-menu.component.html', + styleUrls: ['./event-menu.component.scss'] +}) +export class EventMenuComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/event/event.module.ts b/frontend/projects/admin/src/app/event/event.module.ts new file mode 100644 index 0000000000..fb148989f7 --- /dev/null +++ b/frontend/projects/admin/src/app/event/event.module.ts @@ -0,0 +1,58 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { + NgbCarouselModule, + NgbDropdownModule, + NgbNavModule, + NgbTypeaheadModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { provideSvgIconsConfig, SvgIconComponent } from '@ngneat/svg-icon'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgChartsModule } from 'ng2-charts'; +import { EventService } from '../shared/event.service'; +import { ICON_CONFIG } from '../shared/icons'; +import { OrganizationService } from '../shared/organization.service'; +import { SharedModule } from '../shared/shared.module'; +import { EventDashboardComponent } from './event-dashboard/event-dashboard.component'; +import { EventMenuComponent } from './event-menu/event-menu.component'; +import { TranslatePaymentProxiesPipe } from './translate-payment-proxies.pipe'; +import { EmailLogComponent } from './email-log/email-log.component'; +import { ShowSelectedCategoriesPipe } from './show-selected-categories.pipe'; +import { UiCategoryBuilderPipe } from './ui-category-builder.pipe'; +import { FormsModule } from '@angular/forms'; +import { TicketCategoryDetailComponent } from './ticket-category-detail/ticket-category-detail.component'; + +@NgModule({ + declarations: [ + EventMenuComponent, + EventDashboardComponent, + TranslatePaymentProxiesPipe, + EmailLogComponent, + ShowSelectedCategoriesPipe, + UiCategoryBuilderPipe, + TicketCategoryDetailComponent, + ], + imports: [ + TranslateModule.forChild(), + CommonModule, + SvgIconComponent, + SharedModule, + NgChartsModule, + NgbCarouselModule, + NgbNavModule, + FormsModule, + NgbDropdownModule, + + RouterModule.forChild([ + { path: '', component: EventDashboardComponent }, + { path: 'email-log', component: EmailLogComponent }, + ]), + ], + providers: [ + OrganizationService, + EventService, + provideSvgIconsConfig(ICON_CONFIG), + ], +}) +export class EventModule {} diff --git a/frontend/projects/admin/src/app/event/show-selected-categories.pipe.ts b/frontend/projects/admin/src/app/event/show-selected-categories.pipe.ts new file mode 100644 index 0000000000..10e254d30c --- /dev/null +++ b/frontend/projects/admin/src/app/event/show-selected-categories.pipe.ts @@ -0,0 +1,30 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {TicketCategoryFilter, UiTicketCategory} from '../model/ticket-category'; + + +@Pipe({ + name: 'showSelectedCategories', + pure: false, +}) +export class ShowSelectedCategoriesPipe implements PipeTransform { + transform( + categories: UiTicketCategory[], + criteria: TicketCategoryFilter + ): UiTicketCategory[] { + if (criteria.active && criteria.expired && criteria.search === '') { + return categories; + } + return categories.filter((category) => { + const result = + (criteria.active && !category.expired) || + (criteria.expired && category.expired); + if (result && criteria.search !== '') { + return ( + category.name.toLowerCase().indexOf(criteria.search.toLowerCase()) > + -1 + ); + } + return result; + }); + } +} diff --git a/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.html b/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.html new file mode 100644 index 0000000000..3e6fafed66 --- /dev/null +++ b/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.html @@ -0,0 +1 @@ +<p>ticket-category-detail works!</p> diff --git a/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.scss b/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.ts b/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.ts new file mode 100644 index 0000000000..5134aec103 --- /dev/null +++ b/frontend/projects/admin/src/app/event/ticket-category-detail/ticket-category-detail.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-ticket-category-detail', + templateUrl: './ticket-category-detail.component.html', + styleUrls: ['./ticket-category-detail.component.scss'] +}) +export class TicketCategoryDetailComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/projects/admin/src/app/event/translate-payment-proxies.pipe.ts b/frontend/projects/admin/src/app/event/translate-payment-proxies.pipe.ts new file mode 100644 index 0000000000..0c15a5c04c --- /dev/null +++ b/frontend/projects/admin/src/app/event/translate-payment-proxies.pipe.ts @@ -0,0 +1,22 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +const PAYMENT_PROXY_DESCRIPTIONS = { + STRIPE: 'Stripe: Credit cards', + ON_SITE: 'On site (cash) payment', + OFFLINE: 'Offline payment (bank transfer, invoice, etc.)', + PAYPAL: 'PayPal', + MOLLIE: + 'Mollie: Credit cards, iDEAL, Bancontact, ING Home Pay, Belfius, KBC/CBC, Przelewy24', + SAFERPAY: 'Saferpay By SIX Payments', +} as any; + +@Pipe({ + name: 'translatePaymentProxies', +}) +export class TranslatePaymentProxiesPipe implements PipeTransform { + transform(list: string[]): string[] { + return list.map((item) => + PAYMENT_PROXY_DESCRIPTIONS[item] ? PAYMENT_PROXY_DESCRIPTIONS[item] : item + ); + } +} diff --git a/frontend/projects/admin/src/app/event/ui-category-builder.pipe.ts b/frontend/projects/admin/src/app/event/ui-category-builder.pipe.ts new file mode 100644 index 0000000000..bfc235e3f3 --- /dev/null +++ b/frontend/projects/admin/src/app/event/ui-category-builder.pipe.ts @@ -0,0 +1,12 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {TicketCategory, UiTicketCategory} from '../model/ticket-category'; + + +@Pipe({ + name: 'uiCategoryBuilder', +}) +export class UiCategoryBuilderPipe implements PipeTransform { + transform(categories: TicketCategory[]): UiTicketCategory[] { + return categories.map((category) => new UiTicketCategory(category)); + } +} diff --git a/frontend/projects/admin/src/app/missing-org/missing-org.component.html b/frontend/projects/admin/src/app/missing-org/missing-org.component.html new file mode 100644 index 0000000000..cfd93db722 --- /dev/null +++ b/frontend/projects/admin/src/app/missing-org/missing-org.component.html @@ -0,0 +1 @@ +<p>missing-org works!</p> diff --git a/frontend/projects/admin/src/app/missing-org/missing-org.component.scss b/frontend/projects/admin/src/app/missing-org/missing-org.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/missing-org/missing-org.component.ts b/frontend/projects/admin/src/app/missing-org/missing-org.component.ts new file mode 100644 index 0000000000..9b1f3c15dd --- /dev/null +++ b/frontend/projects/admin/src/app/missing-org/missing-org.component.ts @@ -0,0 +1,25 @@ +import {Component, OnInit} from '@angular/core'; +import {Router} from '@angular/router'; +import {firstValueFrom} from 'rxjs'; +import {OrganizationService} from '../shared/organization.service'; + +@Component({ + selector: 'app-missing-org', + templateUrl: './missing-org.component.html', + styleUrls: ['./missing-org.component.scss'] +}) +export class MissingOrgComponent implements OnInit { + + constructor( + private readonly organizationService: OrganizationService, + private readonly router: Router + ) { } + + ngOnInit(): void { + firstValueFrom(this.organizationService.getOrganizations()).then(orgs => { + if (orgs.length > 0) { + this.router.navigate(['organization', orgs[0].id, 'event']); + } + }); + } +} diff --git a/frontend/projects/admin/src/app/model/apikey-bulk.ts b/frontend/projects/admin/src/app/model/apikey-bulk.ts new file mode 100644 index 0000000000..afdbe34227 --- /dev/null +++ b/frontend/projects/admin/src/app/model/apikey-bulk.ts @@ -0,0 +1,5 @@ +export interface ApiKeyBulk{ + organizationId : number; + role: string; + descriptions: string[]; +} diff --git a/frontend/projects/admin/src/app/model/event.ts b/frontend/projects/admin/src/app/model/event.ts new file mode 100644 index 0000000000..4dc2b4ec03 --- /dev/null +++ b/frontend/projects/admin/src/app/model/event.ts @@ -0,0 +1,70 @@ +import {Organization} from './organization'; +import {TicketCategory} from './ticket-category'; + +export interface EventInfo { + allowedPaymentProxies: string[]; // TODO: enum + availableSeats: number; + checkedInTickets: number; + currency: string; + description: { [lang: string]: string }; + displayName: string; + displayStatistics: boolean; + displayLanguage: string; + contentLanguages: { + displayLanguage: string; + language: string; + }[]; + dynamicAllocation: number; + expired: boolean; + fileBlobId: string; + formattedBegin: string; + formattedEnd: string; + freeOfCharge: boolean; + id: number; + location: string; + notAllocatedTickets: number; + notSoldTickets: number; + organizationId: number; + online: boolean; + pendingTickets: number; + privacyPolicyUrl: string; + releasedTickets: number; + shortName: string; + soldTickets: number; + status: string; // TODO: enum + termsAndConditionsUrl: string; + timeZone: string; + visibleForCurrentUser: boolean; + warningNeeded: boolean; + websiteUrl: string; + grossIncome: number; + regularPrice: number; + finalPrice: number; + vatPercentage: number; + ticketCategories: TicketCategory[]; + format: string; + containingUnboundedCategories: boolean; +} + +export interface EventOrganizationInfo { + event: EventInfo; + organization: Organization; +} + +export interface Event { + id: number; + displayName: string; + shortName: string; +} + +export interface EventTicketsStatistics { + granularity: string; + sold: { + count: number; + date: string; + }[]; + reserved: { + count: number; + date: string; + }[]; +} diff --git a/frontend/projects/admin/src/app/model/instance-settings.ts b/frontend/projects/admin/src/app/model/instance-settings.ts new file mode 100644 index 0000000000..1cf4ce9e8d --- /dev/null +++ b/frontend/projects/admin/src/app/model/instance-settings.ts @@ -0,0 +1,4 @@ +export interface InstanceSetting { + descriptionMaxLength: number; + baseUrl: string; +} diff --git a/frontend/projects/admin/src/app/model/organization.ts b/frontend/projects/admin/src/app/model/organization.ts new file mode 100644 index 0000000000..ee9d7af32d --- /dev/null +++ b/frontend/projects/admin/src/app/model/organization.ts @@ -0,0 +1,8 @@ +export interface Organization { + id: number; + name: string; + description: string; + email: string; + externalId: string | null, + slug: string | null, +} diff --git a/frontend/projects/admin/src/app/model/role.ts b/frontend/projects/admin/src/app/model/role.ts new file mode 100644 index 0000000000..49bd0c5679 --- /dev/null +++ b/frontend/projects/admin/src/app/model/role.ts @@ -0,0 +1,7 @@ +import { RoleType } from "./user"; + +export interface Role { + role: RoleType; + target: string[]; + description: string; +} diff --git a/frontend/projects/admin/src/app/model/ticket-category.ts b/frontend/projects/admin/src/app/model/ticket-category.ts new file mode 100644 index 0000000000..665eac6b9c --- /dev/null +++ b/frontend/projects/admin/src/app/model/ticket-category.ts @@ -0,0 +1,116 @@ +export type TicketAccessType = 'INHERIT' | 'IN_PERSON' | 'ONLINE'; + +export interface TicketTokenStatus { + accessCodeId: number | null; + code: string; + id: number; + priceInCents: number; + recipientEmail: string | null; + recipientName: string | null; + sentTimestamp: string | null; + status: string; + ticketCategoryId: number; +} + +export interface TicketCategory { + accessRestricted: boolean; + availableTickets: number | null; + bounded: boolean; + checkedInTickets: number; + containingOrphans: boolean; + containingStuckTickets: boolean; + description: { [key: string]: string }; + displayTaxInformation: boolean; + expired: boolean; + formattedDiscountedPrice: string; + formattedExpiration: { [key: string]: string }; + formattedFinalPrice: string; + formattedInception: { [key: string]: string }; + free: boolean; + hasDiscount: boolean; + id: number; + maximumSaleableTickets: number; + maxTickets: number; + name: string; + notSoldTickets: number; + pendingTickets: number; + saleableAndLimitNotReached: boolean; + saleInFuture: boolean; + soldOutOrLimitReached: boolean; + soldTickets: number; + ticketAccessType: TicketAccessType; + tokenStatus: TicketTokenStatus[]; +} + +export class UiTicketCategory implements TicketCategory { + accessRestricted: boolean; + availableTickets: number | null; + bounded: boolean; + checkedInTickets: number; + containingOrphans: boolean; + containingStuckTickets: boolean; + description: { [key: string]: string }; + displayTaxInformation: boolean; + displayWarning: boolean; + expired: boolean; + formattedDiscountedPrice: string; + formattedExpiration: { [key: string]: string }; + formattedFinalPrice: string; + formattedInception: { [key: string]: string }; + free: boolean; + hasDiscount: boolean; + id: number; + maximumSaleableTickets: number; + maxTickets: number; + name: string; + notSoldTickets: number; + pendingTickets: number; + saleableAndLimitNotReached: boolean; + saleInFuture: boolean; + soldOutOrLimitReached: boolean; + soldTickets: number; + ticketAccessType: TicketAccessType; + tokenViewExpanded: boolean; + attendeesList: { groupId: number; groupName: string } | null; + tokenStatus: TicketTokenStatus[]; + + constructor(ticketCategory: TicketCategory) { + this.accessRestricted = ticketCategory.accessRestricted; + this.availableTickets = ticketCategory.availableTickets; + this.bounded = ticketCategory.bounded; + this.checkedInTickets = ticketCategory.checkedInTickets; + this.containingOrphans = ticketCategory.containingOrphans; + this.containingStuckTickets = ticketCategory.containingStuckTickets; + this.description = ticketCategory.description; + this.displayTaxInformation = ticketCategory.displayTaxInformation; + this.displayWarning = + ticketCategory.containingStuckTickets || ticketCategory.containingOrphans; + this.expired = ticketCategory.expired; + this.formattedDiscountedPrice = ticketCategory.formattedDiscountedPrice; + this.formattedExpiration = ticketCategory.formattedExpiration; + this.formattedFinalPrice = ticketCategory.formattedFinalPrice; + this.formattedInception = ticketCategory.formattedInception; + this.free = ticketCategory.free; + this.hasDiscount = ticketCategory.hasDiscount; + this.id = ticketCategory.id; + this.maximumSaleableTickets = ticketCategory.maximumSaleableTickets; + this.maxTickets = ticketCategory.maxTickets; + this.name = ticketCategory.name; + this.saleableAndLimitNotReached = ticketCategory.saleableAndLimitNotReached; + this.saleInFuture = ticketCategory.saleInFuture; + this.soldOutOrLimitReached = ticketCategory.soldOutOrLimitReached; + this.soldTickets = ticketCategory.soldTickets; + this.ticketAccessType = ticketCategory.ticketAccessType; + this.pendingTickets = ticketCategory.pendingTickets; + this.notSoldTickets = ticketCategory.notSoldTickets; + this.tokenStatus = ticketCategory.tokenStatus; + this.tokenViewExpanded = false; + this.attendeesList = null; + } +} + +export interface TicketCategoryFilter { + active: boolean; + expired: boolean; + search: string; +} diff --git a/frontend/projects/admin/src/app/model/user.ts b/frontend/projects/admin/src/app/model/user.ts new file mode 100644 index 0000000000..5bf3a02b38 --- /dev/null +++ b/frontend/projects/admin/src/app/model/user.ts @@ -0,0 +1,43 @@ +import { Organization } from "./organization"; + +export interface UserInfo { + id: number; + type: UserType; + username: string; + description: string; + firstName: string; + lastName: string; + emailAddress: string; + role: RoleType; +} + +export enum UserType { + INTERNAL = 'INTERNAL', + DEMO = 'DEMO', + API_KEY = 'API_KEY' +} + +export enum RoleType { + ADMIN = 'ADMIN', + OWNER = 'OWNER', + SUPERVISOR = 'SUPERVISOR', + OPERATOR = 'OPERATOR', + SPONSOR = 'SPONSOR', + API_CONSUMER = 'API_CONSUMER' +} + +export interface User { + id: number; + type: UserType; + enabled: boolean; + validTo: string | null; + username: string; + firstName: string; + lastName: string; + emailAddress: string; + validToEpochSecond: number | null; + description: string | null; + roles: RoleType[]; + memberOf: Organization[]; +} + diff --git a/frontend/projects/admin/src/app/org-selector/org-selector.component.html b/frontend/projects/admin/src/app/org-selector/org-selector.component.html new file mode 100644 index 0000000000..50ae90bd21 --- /dev/null +++ b/frontend/projects/admin/src/app/org-selector/org-selector.component.html @@ -0,0 +1,39 @@ +<div class="modal-header"> + <h4 class="modal-title" translate="admin.organization.select"></h4> + <button + type="button" + class="btn-close" + aria-label="Close" + (click)="activeModal.dismiss('Cross click')" + ></button> +</div> +<div class="modal-body"> + <table class="table table-striped"> + <thead> + <tr> + <th scope="col">Name</th> + <th scope="col">Description</th> + <th scope="col">Email</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let org of organizations$ | async"> + <td scope="row"> + <button class="btn btn-link" (click)="selectOrganization(org)"> + {{ org.name }} + </button> + </td> + <td>{{ org.description }}</td> + <td>{{ org.email }}</td> + </tr> + </tbody> + </table> +</div> +<div class="modal-footer"> + <button + type="button" + class="btn btn-outline-dark" + (click)="activeModal.close('Close click')" + translate="common.close" + ></button> +</div> diff --git a/frontend/projects/admin/src/app/org-selector/org-selector.component.scss b/frontend/projects/admin/src/app/org-selector/org-selector.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/org-selector/org-selector.component.ts b/frontend/projects/admin/src/app/org-selector/org-selector.component.ts new file mode 100644 index 0000000000..1e4ee9b4d1 --- /dev/null +++ b/frontend/projects/admin/src/app/org-selector/org-selector.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Observable } from 'rxjs'; +import { Organization } from '../model/organization'; + +@Component({ + selector: 'app-org-selector', + templateUrl: './org-selector.component.html', + styleUrls: ['./org-selector.component.scss'], +}) +export class OrgSelectorComponent implements OnInit { + public organizations$?: Observable<Organization[]>; + public organizationId$?: Observable<string | null>; + + constructor(public activeModal: NgbActiveModal) {} + + ngOnInit(): void {} + + public selectOrganization(org: Organization): void { + this.activeModal.close(org); + } +} diff --git a/frontend/projects/admin/src/app/organization-edit/organization-edit.component.html b/frontend/projects/admin/src/app/organization-edit/organization-edit.component.html new file mode 100644 index 0000000000..4b359fa48c --- /dev/null +++ b/frontend/projects/admin/src/app/organization-edit/organization-edit.component.html @@ -0,0 +1,114 @@ +<div class="container"> + <h1 class="my-4"> + <span *ngIf="!editMode" translate="admin.organization.new.title"></span> + <span *ngIf="editMode" + >Edit organization {{ (organization$ | async)?.name }}</span + > + </h1> +</div> + +<div class="container-sm"> + <form [formGroup]="organizationForm" (ngSubmit)="save()"> + <h2 class="my-3" translate="admin.organization.new.organizer-data"></h2> + <hr /> + <div class="form-group my-3"> + <label for="organizationName" translate="admin.common.name"></label> + <input + id="organizationName" + type="text" + class="form-control" + [class.is-invalid]=" + organizationName?.invalid && + (organizationName?.dirty || organizationName?.touched) + " + formControlName="name" + /> + </div> + <div class="form-group my-3"> + <label for="organizationEmail" translate="common.email"></label> + <input + id="organizationEmail" + type="email" + class="form-control" + [class.is-invalid]=" + organizationEmail?.invalid && + (organizationEmail?.dirty || organizationEmail?.touched) + " + formControlName="email" + /> + </div> + <div class="form-group my-3"> + <label + for="organizationDescription" + translate="admin.common.description" + ></label> + <textarea + id="organizationDescription" + class="form-control" + [class.is-invalid]=" + organizationDescription?.invalid && + (organizationDescription?.dirty || organizationDescription?.touched) + " + formControlName="description" + > + </textarea> + </div> + <h2 + class="my-4" + translate="admin.organization.new.advanced-configuration" + ></h2> + <hr /> + <div class="form-group my-3"> + <label + for="organizationSlug" + translate="admin.organization.new.slug" + ></label> + <div class="input-group"> + <div class="input-group-prepend"> + <span class="input-group-text" + >{{ (instanceSetting$ | async)?.baseUrl }}/o/</span + > + </div> + <input + type="text" + class="form-control" + id="organizationSlug" + formControlName="slug" + /> + <div class="input-group-append"> + <span class="input-group-text">/</span> + </div> + </div> + <div class="form-group my-3"> + <label + for="organizationExternalId" + translate="admin.organization.new.external-id" + > + </label> + <input + id="organizationExternalId" + type="text" + class="form-control" + formControlName="externalId" + /> + </div> + </div> + <div class="d-flex justify-content-between"> + <div> + <button + class="btn btn-lg btn-outline-secondary" + [routerLink]="['/organizations']" + translate="admin.common.cancel" + ></button> + </div> + <div> + <button + type="submit" + class="btn btn-lg btn-primary" + [disabled]="organizationForm.invalid" + translate="admin.common.save" + ></button> + </div> + </div> + </form> +</div> diff --git a/frontend/projects/admin/src/app/organization-edit/organization-edit.component.scss b/frontend/projects/admin/src/app/organization-edit/organization-edit.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/organization-edit/organization-edit.component.ts b/frontend/projects/admin/src/app/organization-edit/organization-edit.component.ts new file mode 100644 index 0000000000..30d935882b --- /dev/null +++ b/frontend/projects/admin/src/app/organization-edit/organization-edit.component.ts @@ -0,0 +1,81 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { OrganizationService } from '../shared/organization.service'; +import { Observable, of } from 'rxjs'; +import { Organization } from '../model/organization'; +import { ConfigurationService } from '../shared/configuration.service'; +import { InstanceSetting } from '../model/instance-settings'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Validators } from '@angular/forms'; + +@Component({ + selector: 'app-organization-edit', + templateUrl: './organization-edit.component.html', + styleUrls: ['./organization-edit.component.scss'], +}) +export class OrganizationEditComponent implements OnInit { + public organizationId: string | null = null; + public organization$: Observable<Organization | null> = of(); + public editMode: boolean | undefined; + public instanceSetting$: Observable<InstanceSetting> = of(); + public organizationForm: FormGroup; + + constructor( + private readonly route: ActivatedRoute, + private readonly organizationService: OrganizationService, + private readonly configurationService: ConfigurationService, + formBuilder: FormBuilder, + private readonly router: Router + ) { + this.organizationForm = formBuilder.group({ + id: [null], + name: [null, Validators.required], + email: [null, Validators.required], + description: [null, Validators.required], + slug: [], + externalId: [], + }); + } + + get organizationName() { + return this.organizationForm.get('name'); + } + get organizationEmail() { + return this.organizationForm.get('email'); + } + get organizationDescription() { + return this.organizationForm.get('description'); + } + + ngOnInit(): void { + this.organizationId = this.route.snapshot.paramMap.get('organizationId'); + this.instanceSetting$ = this.configurationService.loadInstanceSetting(); + + if (this.organizationId !== null) { + this.editMode = true; + this.organization$ = this.organizationService.getOrganization( + this.organizationId + ); + this.organization$.subscribe((org) => { + if (org) this.organizationForm.patchValue(org); + }); + } else { + this.editMode = false; + } + } + + save(): void { + let result: Observable<any>; + if (this.editMode) { + result = this.organizationService.update(this.organizationForm.value); + } else { + result = this.organizationService.create(this.organizationForm.value); + } + + result.subscribe((res) => { + if (res === 'OK') { + this.router.navigate(['/organizations']); + } + }); + } +} diff --git a/frontend/projects/admin/src/app/organizations/organizations.component.html b/frontend/projects/admin/src/app/organizations/organizations.component.html new file mode 100644 index 0000000000..07fb539c8a --- /dev/null +++ b/frontend/projects/admin/src/app/organizations/organizations.component.html @@ -0,0 +1,40 @@ +<div class="container"> + <h1 class="my-3" translate="admin.organization.list.title"></h1> + <hr /> + <div class="d-flex flex-row-reverse py-3"> + <button + type="button" + class="btn btn-success d-flex align-items-center" + [routerLink]="['/organizations/new']" + > + <svg-icon key="add" class="d-flex align-items-center"></svg-icon> + <span translate="admin.common.add-new"></span> + </button> + </div> +</div> +<div class="container"> + <table class="table table-striped"> + <thead> + <tr> + <th scope="col" translate="admin.common.name"></th> + <th scope="col" translate="admin.common.description"></th> + <th scope="col" translate="common.email"></th> + <th scope="col" class="col-edit" translate="common.edit"></th> + </tr> + </thead> + <tbody> + <tr *ngFor="let org of organizations$ | async"> + <td scope="row"> + {{ org.name }} + </td> + <td>{{ org.description }}</td> + <td>{{ org.email }}</td> + <td> + <a [routerLink]="['/organizations', org.id, 'edit']" class="btn" + ><svg-icon key="edit" size="lg"></svg-icon + ></a> + </td> + </tr> + </tbody> + </table> +</div> diff --git a/frontend/projects/admin/src/app/organizations/organizations.component.scss b/frontend/projects/admin/src/app/organizations/organizations.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/organizations/organizations.component.ts b/frontend/projects/admin/src/app/organizations/organizations.component.ts new file mode 100644 index 0000000000..f4c8be1487 --- /dev/null +++ b/frontend/projects/admin/src/app/organizations/organizations.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core'; +import { OrganizationService } from '../shared/organization.service'; +import { Observable } from 'rxjs'; +import { Organization } from '../model/organization'; + +@Component({ + selector: 'app-organizations', + templateUrl: './organizations.component.html', + styleUrls: ['./organizations.component.scss'], +}) +export class OrganizationsComponent implements OnInit { + public organizations$?: Observable<Organization[]>; + + constructor(private readonly organizationService: OrganizationService) {} + + ngOnInit(): void { + this.organizations$ = this.organizationService.getOrganizations(); + } +} diff --git a/frontend/projects/admin/src/app/shared/configuration.service.ts b/frontend/projects/admin/src/app/shared/configuration.service.ts new file mode 100644 index 0000000000..b4aa801675 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/configuration.service.ts @@ -0,0 +1,17 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { InstanceSetting } from '../model/instance-settings'; + +@Injectable({ + providedIn: 'root', +}) +export class ConfigurationService { + constructor(private httpClient: HttpClient) {} + + loadInstanceSetting(): Observable<InstanceSetting> { + return this.httpClient.get<InstanceSetting>( + '/admin/api/configuration/instance-settings' + ); + } +} diff --git a/frontend/projects/admin/src/app/shared/event.service.ts b/frontend/projects/admin/src/app/shared/event.service.ts new file mode 100644 index 0000000000..1c7b5c819e --- /dev/null +++ b/frontend/projects/admin/src/app/shared/event.service.ts @@ -0,0 +1,55 @@ +import {HttpClient} from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {map, Observable} from 'rxjs'; +import {Event, EventInfo, EventOrganizationInfo, EventTicketsStatistics,} from '../model/event'; +import {UiTicketCategory} from '../model/ticket-category'; + + +@Injectable() +export class EventService { + constructor(private httpClient: HttpClient) {} + + getActiveEvents(organizationId: string): Observable<EventInfo[]> { + const orgId = parseInt(organizationId); + return this.httpClient + .get<EventInfo[]>('/admin/api/active-events') + .pipe(map((events) => events.filter((e) => e.organizationId === orgId))); + } + + getExpiredEvents(organizationId: string): Observable<EventInfo[]> { + const orgId = parseInt(organizationId); + return this.httpClient + .get<EventInfo[]>('/admin/api/expired-events') + .pipe(map((events) => events.filter((e) => e.organizationId == orgId))); + } + + getEvent(eventId: string): Observable<Event> { + return this.httpClient.get<Event>(`/admin/api/events/id/${eventId}`); + } + + getEventByShortName( + eventShortName: string + ): Observable<EventOrganizationInfo> { + return this.httpClient.get<EventOrganizationInfo>( + `/admin/api/events/${eventShortName}` + ); + } + + getTicketsStatistics( + eventShortName: string + ): Observable<EventTicketsStatistics> { + return this.httpClient.get<EventTicketsStatistics>( + `/admin/api/events/${eventShortName}/ticket-sold-statistics` + ); + } + unbindTickets(eventShortName: string, category: UiTicketCategory) { + return this.httpClient.put( + '/admin/api/events/' + + eventShortName + + '/category/' + + category.id + + '/unbind-tickets', + null + ); + } +} diff --git a/frontend/projects/admin/src/app/shared/filter-button/filter-button.component.ts b/frontend/projects/admin/src/app/shared/filter-button/filter-button.component.ts new file mode 100644 index 0000000000..23a53b5e8b --- /dev/null +++ b/frontend/projects/admin/src/app/shared/filter-button/filter-button.component.ts @@ -0,0 +1,36 @@ +import {NgClass, NgIf} from '@angular/common'; +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {SvgIconComponent} from '@ngneat/svg-icon'; + +@Component({ + standalone: true, + imports: [SvgIconComponent, NgClass, NgIf], + selector: 'app-filter-button', + template: ` + <button + type="button" + class="btn" + style="border-radius: 20px" + [ngClass]="{'btn-outline-primary': !checked, 'btn-primary': checked}" + (click)="onClick()" + > + <svg-icon key="check" size="md" *ngIf="checked"></svg-icon> {{text}} + </button>`, +}) +export class FilterButtonComponent { + + @Input() + public text?: string; + + @Input() + public checked: boolean = false; + + @Output() + public toggleFilter = new EventEmitter<boolean>(); + + constructor() { } + + public onClick(): void { + this.toggleFilter.emit(!this.checked); + } +} diff --git a/frontend/projects/admin/src/app/shared/http-login.interceptor.ts b/frontend/projects/admin/src/app/shared/http-login.interceptor.ts new file mode 100644 index 0000000000..10d5aee1d2 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/http-login.interceptor.ts @@ -0,0 +1,40 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; +import {catchError, Observable, throwError} from "rxjs"; +import {Inject, Injectable, Injector, INJECTOR} from "@angular/core"; +import {environment} from "../../environments/environment"; +import {Router} from "@angular/router"; + +@Injectable() +export class HttpLoginInterceptor implements HttpInterceptor { + + constructor(@Inject(INJECTOR) private injector: Injector) { + } + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + // set X-Requested-With to XMLHttpRequest to match checks on the backend + const clonedRequest = req.clone({ + headers: req.headers.set("X-Requested-With", "XMLHttpRequest") + }); + return next.handle(clonedRequest) + .pipe(catchError(err => { + if (err.status === 401) { + // user authentication is not valid anymore. + // let's redirect it to the authentication resource + redirectToLogin(this.injector.get(Router)).then(() => {}) + } + return throwError(() => err); + })); + } + +} + +export function redirectToLogin(router: Router): Promise<boolean> { + if (environment.production) { + // reload the current location to trigger server-side authentication + window.location.reload(); + } else { + // dev mode: redirect to the /authentication local resource + return router.navigate(['./authentication']); + } + return Promise.resolve(true); +} diff --git a/frontend/projects/admin/src/app/shared/i18n.service.ts b/frontend/projects/admin/src/app/shared/i18n.service.ts new file mode 100644 index 0000000000..9938f7e716 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/i18n.service.ts @@ -0,0 +1,18 @@ +import {HttpClient} from "@angular/common/http"; +import {TranslateLoader} from "@ngx-translate/core"; +import {Observable, shareReplay} from "rxjs"; + +const translationCache: {[key: string]: Observable<any>} = {}; + +export class CustomLoader implements TranslateLoader { + + constructor(private http: HttpClient) { + } + + getTranslation(lang: string): Observable<any> { + if (!translationCache[lang]) { + translationCache[lang] = this.http.get(`/api/v2/admin/i18n/bundle/${lang}`).pipe(shareReplay(1)); + } + return translationCache[lang]; + } +} diff --git a/frontend/projects/admin/src/app/shared/icons.ts b/frontend/projects/admin/src/app/shared/icons.ts new file mode 100644 index 0000000000..7377ee53f3 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/icons.ts @@ -0,0 +1,98 @@ +import { accessIcon } from '../svg/access'; +import { accountcircleIcon } from '../svg/accountcircle'; +import { addIcon } from '../svg/add'; +import { addCircleNewIcon } from '../svg/add-circle-new'; +import { arrowBackIcon } from '../svg/arrow-back'; +import { arrowdropdownIcon } from '../svg/arrowdropdown'; +import { calendarEventIcon } from '../svg/calendar-event'; +import { categoryIcon } from '../svg/category'; +import { checkIcon } from '../svg/check'; +import { closeIcon } from '../svg/close'; +import { deleteIcon } from '../svg/delete'; +import { descriptionIcon } from '../svg/description'; +import { detailsIcon } from '../svg/details'; +import { downloadIcon } from '../svg/download'; +import { editIcon } from '../svg/edit'; +import { emailSentIcon } from '../svg/email-sent'; +import { eventIcon } from '../svg/event'; +import { groupsIcon } from '../svg/groups'; +import { homeIcon } from '../svg/home'; +import { hourglassTopIcon } from '../svg/hourglass-top'; +import { infoIcon } from '../svg/info'; +import { insightsIcon } from '../svg/insights'; +import { keyIcon } from '../svg/key'; +import { lockIcon } from '../svg/lock'; +import { lockOpenIcon } from '../svg/lock-open'; +import { organizationIcon } from '../svg/organization'; +import { outgoingMailIcon } from '../svg/outgoing-mail'; +import { paymentsIcon } from '../svg/payments'; +import { percentIcon } from '../svg/percent'; +import { piggyBankIcon } from '../svg/piggy-bank'; +import { qrCodeIcon } from '../svg/qr-code'; +import { resetIcon } from '../svg/reset'; +import { searchIcon } from '../svg/search'; +import { settingsIcon } from '../svg/settings'; +import { subscriptionIcon } from '../svg/subscription'; +import { ticketIcon } from '../svg/ticket'; +import { ticketReservedIcon } from '../svg/ticket-reserved'; +import { visibilityOnIcon } from '../svg/visibility-on'; +import { visibilityoffIcon } from '../svg/visibilityoff'; +import { waitingListIcon } from '../svg/waiting-list'; +import { warningIcon } from '../svg/warning'; + +const ICONS = [ + accessIcon, + accountcircleIcon, + addCircleNewIcon, + addIcon, + arrowBackIcon, + arrowdropdownIcon, + calendarEventIcon, + categoryIcon, + checkIcon, + deleteIcon, + descriptionIcon, + detailsIcon, + downloadIcon, + editIcon, + emailSentIcon, + eventIcon, + groupsIcon, + homeIcon, + hourglassTopIcon, + insightsIcon, + keyIcon, + lockIcon, + lockOpenIcon, + organizationIcon, + paymentsIcon, + percentIcon, + piggyBankIcon, + resetIcon, + searchIcon, + settingsIcon, + subscriptionIcon, + ticketIcon, + ticketReservedIcon, + visibilityoffIcon, + visibilityOnIcon, + waitingListIcon, + lockIcon, + outgoingMailIcon, + qrCodeIcon, + warningIcon, + closeIcon, + infoIcon, +]; +export const ICON_CONFIG = { + sizes: { + xs: '10px', + sm: '12px', + md: '16px', + lg: '20px', + xl: '25px', + xxl: '30px', + }, + defaultSize: 'xl', + icons: ICONS, +}; diff --git a/frontend/projects/admin/src/app/shared/organization.service.ts b/frontend/projects/admin/src/app/shared/organization.service.ts new file mode 100644 index 0000000000..4055932746 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/organization.service.ts @@ -0,0 +1,28 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Organization } from '../model/organization'; + +@Injectable() +export class OrganizationService { + constructor(private httpClient: HttpClient) {} + + getOrganizations(): Observable<Organization[]> { + return this.httpClient.get<Organization[]>('/admin/api/organizations'); + } + + getOrganization(id: number | string): Observable<Organization> { + return this.httpClient.get<Organization>(`/admin/api/organizations/${id}`); + } + + create(organization: Organization): Observable<any> { + return this.httpClient.post<any>( + '/admin/api/organizations/new', + organization + ); + } + + update(organization : Organization) : Observable<any> { + return this.httpClient.post<any>('/admin/api/organizations/update', organization); + } +} diff --git a/frontend/projects/admin/src/app/shared/section-dashboard/section-dashboard.component.html b/frontend/projects/admin/src/app/shared/section-dashboard/section-dashboard.component.html new file mode 100644 index 0000000000..d649b435da --- /dev/null +++ b/frontend/projects/admin/src/app/shared/section-dashboard/section-dashboard.component.html @@ -0,0 +1,12 @@ +<div class="row h-100"> + <div class="col-md-2" *ngIf="hasMenu"> + <nav> + <ng-content select="[menu]"></ng-content> + </nav> + </div> + <div class="col-md-10" [class.flex-grow-1]="!hasMenu"> + <main> + <ng-content></ng-content> + </main> + </div> +</div> diff --git a/frontend/projects/admin/src/app/shared/section-dashboard/section-dashboard.component.ts b/frontend/projects/admin/src/app/shared/section-dashboard/section-dashboard.component.ts new file mode 100644 index 0000000000..e1161d8dd9 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/section-dashboard/section-dashboard.component.ts @@ -0,0 +1,15 @@ +import {Component, Input} from "@angular/core"; + +@Component({ + selector: 'app-section-dashboard', + templateUrl: './section-dashboard.component.html' +}) +export class SectionDashboardComponent { + + @Input() + hasMenu = false; + +} + + + diff --git a/frontend/projects/admin/src/app/shared/shared.module.ts b/frontend/projects/admin/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..49cf86aae1 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/shared.module.ts @@ -0,0 +1,22 @@ +import {NgModule} from "@angular/core"; +import {SectionDashboardComponent} from "./section-dashboard/section-dashboard.component"; +import {UserNamePipe} from "./user-name.pipe"; +import {NgIf} from "@angular/common"; +import { UsersFilterOrgPipe } from "./users-filter-org.pipe"; + +@NgModule({ + declarations: [ + SectionDashboardComponent, + UserNamePipe, + UsersFilterOrgPipe, + ], + imports: [ + NgIf + ], + exports: [ + SectionDashboardComponent, + UserNamePipe, + UsersFilterOrgPipe, + ] +}) +export class SharedModule {} diff --git a/frontend/projects/admin/src/app/shared/user-name.pipe.ts b/frontend/projects/admin/src/app/shared/user-name.pipe.ts new file mode 100644 index 0000000000..6a2ca84765 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/user-name.pipe.ts @@ -0,0 +1,16 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import {UserInfo} from "../model/user"; + +@Pipe({ + name: 'userName', + pure: true +}) +export class UserNamePipe implements PipeTransform { + transform(user: UserInfo | null): string { + if (user != null) { + return user.firstName + " " + user.lastName; + } + return ""; + } + +} diff --git a/frontend/projects/admin/src/app/shared/user.service.ts b/frontend/projects/admin/src/app/shared/user.service.ts new file mode 100644 index 0000000000..853f06fb7b --- /dev/null +++ b/frontend/projects/admin/src/app/shared/user.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { catchError, filter, map, Observable, of } from 'rxjs'; +import { User, UserInfo} from '../model/user'; +import { Role } from '../model/role'; +import { ApiKeyBulk } from '../model/apikey-bulk'; + +@Injectable() +export class UserService { + constructor(private httpClient: HttpClient) {} + + public checkUserLoggedIn(): Observable<boolean> { + return this.httpClient + .get<{ authenticated: boolean }>('/authentication-status', { + observe: 'response', + }) + .pipe( + map((resp) => { + return resp.status === 200 && (resp.body?.authenticated || false); + }), + catchError((err) => { + console.log('error!', err); + return of(false); + }) + ); + } + + getCurrent(): Observable<UserInfo> { + return this.httpClient.get<UserInfo>('/admin/api/users/current'); + } + + getAllUsers(): Observable<User[]> { + return this.getAll().pipe( + map((users) => users.filter((user) => user.type !== 'API_KEY')) + ); + } + + getAllApiKey(): Observable<User[]> { + return this.getAll().pipe( + map((users) => users.filter((user) => user.type === 'API_KEY')) + ); + } + + getAll(): Observable<User[]> { + return this.httpClient.get<User[]>('/admin/api/users'); + } + + enable(user: User, enabled: boolean): Observable<string> { + return this.httpClient.post<string>( + `/admin/api/users/${user.id}/enable/${enabled}`, + {} + ); + } + + deleteUser(user: User): Observable<string> { + return this.httpClient.delete<string>(`/admin/api/users/${user.id}`); + } + + getAllRoles(): Observable<Role[]> { + return this.httpClient.get<Role[]>('/admin/api/roles'); + } + + create(user: User): Observable<any> { + return this.httpClient.post<any>('/admin/api/users/new', user, { + params: { baseUrl: window.location.origin }, + }); + } + + getUser(id: number | string) : Observable<User>{ + return this.httpClient.get<User>(`/admin/api/users/${id}`); + } + + update(user: User) : Observable<any>{ + return this.httpClient.post<any>('/admin/api/users/edit', user); + } + + createApiBulk(apiKeyBulk: ApiKeyBulk): Observable<any>{ + return this.httpClient.post<any>('/admin/api/api-keys/bulk', apiKeyBulk); + } +} diff --git a/frontend/projects/admin/src/app/shared/users-filter-org.pipe.ts b/frontend/projects/admin/src/app/shared/users-filter-org.pipe.ts new file mode 100644 index 0000000000..ea2789bb49 --- /dev/null +++ b/frontend/projects/admin/src/app/shared/users-filter-org.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { User } from '../model/user'; +import { Organization } from '../model/organization'; + +@Pipe({ + name: 'usersFilterOrg', + pure: true, +}) +export class UsersFilterOrgPipe implements PipeTransform { + transform( + value: User[] | null, + org: Organization | undefined + ): User[] | null { + if (!value) { + return value; + } + return value.filter((user) => { + if (!org) { + return true; + } + return user.memberOf.some( + (userOrganization) => userOrganization.id === org.id + ); + }); + } +} diff --git a/frontend/projects/admin/src/app/svg/access.ts b/frontend/projects/admin/src/app/svg/access.ts new file mode 100644 index 0000000000..f37971a8e6 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/access.ts @@ -0,0 +1,4 @@ +export const accessIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M24 44q-7-1.75-11.5-8.125T8 21.9V10l16-6 16 6v11.9q0 7.6-4.5 13.975T24 44Zm0-3.1q5.3-1.75 8.775-6.425Q36.25 29.8 36.85 24H24V7.25L11 12.1v9.8q0 .6.025 1.025.025.425.125 1.075H24Z"/></svg>`, + name: 'access' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/accountcircle.ts b/frontend/projects/admin/src/app/svg/accountcircle.ts new file mode 100644 index 0000000000..99f45a0c04 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/accountcircle.ts @@ -0,0 +1,4 @@ +export const accountcircleIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M11.1 35.25q3.15-2.2 6.25-3.375Q20.45 30.7 24 30.7q3.55 0 6.675 1.175t6.275 3.375q2.2-2.7 3.125-5.45Q41 27.05 41 24q0-7.25-4.875-12.125T24 7q-7.25 0-12.125 4.875T7 24q0 3.05.95 5.8t3.15 5.45ZM24 25.5q-2.9 0-4.875-1.975T17.15 18.65q0-2.9 1.975-4.875T24 11.8q2.9 0 4.875 1.975t1.975 4.875q0 2.9-1.975 4.875T24 25.5ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 24q0-4.15 1.575-7.775t4.3-6.35q2.725-2.725 6.375-4.3Q19.9 4 24 4q4.15 0 7.775 1.575t6.35 4.3q2.725 2.725 4.3 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.3 6.375-2.725 2.725-6.35 4.3Q28.15 44 24 44Zm0-3q2.75 0 5.375-.8t5.175-2.8q-2.55-1.8-5.2-2.75-2.65-.95-5.35-.95-2.7 0-5.35.95-2.65.95-5.2 2.75 2.55 2 5.175 2.8Q21.25 41 24 41Zm0-18.5q1.7 0 2.775-1.075t1.075-2.775q0-1.7-1.075-2.775T24 14.8q-1.7 0-2.775 1.075T20.15 18.65q0 1.7 1.075 2.775T24 22.5Zm0-3.85Zm0 18.7Z"/></svg>`, + name: 'accountcircle' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/add-circle-new.ts b/frontend/projects/admin/src/app/svg/add-circle-new.ts new file mode 100644 index 0000000000..8a405ac1c1 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/add-circle-new.ts @@ -0,0 +1,4 @@ +export const addCircleNewIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M453 776h60V610h167v-60H513V376h-60v174H280v60h173v166Zm27.266 200q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z"/></svg>`, + name: 'add-circle-new' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/add.ts b/frontend/projects/admin/src/app/svg/add.ts new file mode 100644 index 0000000000..da716c2bc8 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/add.ts @@ -0,0 +1,4 @@ +export const addIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M22.5 38V25.5H10v-3h12.5V10h3v12.5H38v3H25.5V38Z"/></svg>`, + name: 'add' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/arrow-back.ts b/frontend/projects/admin/src/app/svg/arrow-back.ts new file mode 100644 index 0000000000..ab150e0245 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/arrow-back.ts @@ -0,0 +1,4 @@ +export const arrowBackIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M480-160 160-480l320-320 42 42-248 248h526v60H274l248 248-42 42Z"/></svg>`, + name: 'arrow-back' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/arrowdropdown.ts b/frontend/projects/admin/src/app/svg/arrowdropdown.ts new file mode 100644 index 0000000000..2dae240495 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/arrowdropdown.ts @@ -0,0 +1,4 @@ +export const arrowdropdownIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="m24 30-10-9.95h20Z"/></svg>`, + name: 'arrowdropdown' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/calendar-event.ts b/frontend/projects/admin/src/app/svg/calendar-event.ts new file mode 100644 index 0000000000..d33091e601 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/calendar-event.ts @@ -0,0 +1,4 @@ +export const calendarEventIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M596.817-220Q556-220 528-248.183q-28-28.183-28-69T528.183-386q28.183-28 69-28T666-385.817q28 28.183 28 69T665.817-248q-28.183 28-69 28ZM180-80q-24 0-42-18t-18-42v-620q0-24 18-42t42-18h65v-60h65v60h340v-60h65v60h65q24 0 42 18t18 42v620q0 24-18 42t-42 18H180Zm0-60h600v-430H180v430Zm0-490h600v-130H180v130Zm0 0v-130 130Z"/></svg>`, + name: 'calendar-event' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/category.ts b/frontend/projects/admin/src/app/svg/category.ts new file mode 100644 index 0000000000..d0be84781e --- /dev/null +++ b/frontend/projects/admin/src/app/svg/category.ts @@ -0,0 +1,4 @@ +export const categoryIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m261-526 220-354 220 354H261ZM706-80q-74 0-124-50t-50-124q0-74 50-124t124-50q74 0 124 50t50 124q0 74-50 124T706-80Zm-586-25v-304h304v304H120Zm586.085-35Q754-140 787-173.085q33-33.084 33-81Q820-302 786.916-335q-33.085-33-81.001-33Q658-368 625-334.915q-33 33.084-33 81Q592-206 625.084-173q33.085 33 81.001 33ZM180-165h184v-184H180v184Zm189-421h224L481-767 369-586Zm112 0ZM364-349Zm342 95Z"/></svg>`, + name: 'category' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/check.ts b/frontend/projects/admin/src/app/svg/check.ts new file mode 100644 index 0000000000..2efa6b4b4b --- /dev/null +++ b/frontend/projects/admin/src/app/svg/check.ts @@ -0,0 +1,4 @@ +export const checkIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M18.9 35.7 7.7 24.5l2.15-2.15 9.05 9.05 19.2-19.2 2.15 2.15Z"/></svg>`, + name: 'check' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/close.ts b/frontend/projects/admin/src/app/svg/close.ts new file mode 100644 index 0000000000..64683a5a67 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/close.ts @@ -0,0 +1,4 @@ +export const closeIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z"/></svg>`, + name: 'close' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/delete.ts b/frontend/projects/admin/src/app/svg/delete.ts new file mode 100644 index 0000000000..773b1d033d --- /dev/null +++ b/frontend/projects/admin/src/app/svg/delete.ts @@ -0,0 +1,4 @@ +export const deleteIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M261 936q-24.75 0-42.375-17.625T201 876V306h-41v-60h188v-30h264v30h188v60h-41v570q0 24-18 42t-42 18H261Zm438-630H261v570h438V306ZM367 790h60V391h-60v399Zm166 0h60V391h-60v399ZM261 306v570-570Z"/></svg>`, + name: 'delete' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/description.ts b/frontend/projects/admin/src/app/svg/description.ts new file mode 100644 index 0000000000..f2e4423d29 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/description.ts @@ -0,0 +1,4 @@ +export const descriptionIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M319-250h322v-60H319v60Zm0-170h322v-60H319v60ZM220-80q-24 0-42-18t-18-42v-680q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554v-186H220v680h520v-494H551ZM220-820v186-186 680-680Z"/></svg>`, + name: 'description' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/details.ts b/frontend/projects/admin/src/app/svg/details.ts new file mode 100644 index 0000000000..df49ed70ef --- /dev/null +++ b/frontend/projects/admin/src/app/svg/details.ts @@ -0,0 +1,4 @@ +export const detailsIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="m80 936 400-720 400 720H80Zm102-60h268V394L182 876Zm328 0h268L510 394v482Z"/></svg>`, + name: 'details' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/download.ts b/frontend/projects/admin/src/app/svg/download.ts new file mode 100644 index 0000000000..934ea6c6a6 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/download.ts @@ -0,0 +1,4 @@ +export const downloadIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M220 896q-24 0-42-18t-18-42V693h60v143h520V693h60v143q0 24-18 42t-42 18H220Zm260-153L287 550l43-43 120 120V256h60v371l120-120 43 43-193 193Z"/></svg>`, + name: 'download' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/edit.ts b/frontend/projects/admin/src/app/svg/edit.ts new file mode 100644 index 0000000000..6374ac0a39 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/edit.ts @@ -0,0 +1,4 @@ +export const editIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M180 876h44l443-443-44-44-443 443v44Zm614-486L666 262l84-84 128 128-84 84ZM120 936V808l504-504 128 128-504 504H120Zm525-525-22-22 44 44-22-22Z"/></svg>`, + name: 'edit' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/email-sent.ts b/frontend/projects/admin/src/app/svg/email-sent.ts new file mode 100644 index 0000000000..81353de4f3 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/email-sent.ts @@ -0,0 +1,4 @@ +export const emailSentIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-160v-640l760 320-760 320Zm60-93 544-227-544-230v168l242 62-242 60v167Zm0 0v-457 457Z"/></svg>`, + name: 'email-sent' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/event.ts b/frontend/projects/admin/src/app/svg/event.ts new file mode 100644 index 0000000000..214e5ac0d0 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/event.ts @@ -0,0 +1,4 @@ +export const eventIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M7 40q-1.3 0-2.15-.85Q4 38.3 4 37v-7.65q1.85-.4 3.075-1.875T8.3 24q0-2-1.225-3.5T4 18.65V11q0-1.3.85-2.15Q5.7 8 7 8h34q1.3 0 2.15.85Q44 9.7 44 11v7.65q-1.85.35-3.075 1.85T39.7 24q0 2 1.225 3.475T44 29.35V37q0 1.3-.85 2.15Q42.3 40 41 40Zm0-3h34v-5.45q-1.9-1.3-3.1-3.25-1.2-1.95-1.2-4.3 0-2.35 1.2-4.3 1.2-1.95 3.1-3.25V11H7v5.45q1.95 1.3 3.125 3.25T11.3 24q0 2.35-1.175 4.3Q8.95 30.25 7 31.55Zm17-3.15q.6 0 1.05-.45.45-.45.45-1.05 0-.6-.45-1.05-.45-.45-1.05-.45-.6 0-1.05.45-.45.45-.45 1.05 0 .6.45 1.05.45.45 1.05.45Zm0-8.35q.6 0 1.05-.45.45-.45.45-1.05 0-.6-.45-1.05-.45-.45-1.05-.45-.6 0-1.05.45-.45.45-.45 1.05 0 .6.45 1.05.45.45 1.05.45Zm0-8.35q.6 0 1.05-.45.45-.45.45-1.05 0-.6-.45-1.05-.45-.45-1.05-.45-.6 0-1.05.45-.45.45-.45 1.05 0 .6.45 1.05.45.45 1.05.45ZM24 24Z"/></svg>`, + name: 'event' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/groups.ts b/frontend/projects/admin/src/app/svg/groups.ts new file mode 100644 index 0000000000..c3c23de539 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/groups.ts @@ -0,0 +1,4 @@ +export const groupsIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M0 36v-2.65q0-1.95 2.1-3.15T7.5 29q.65 0 1.2.025.55.025 1.1.125-.4.85-.6 1.725-.2.875-.2 1.875V36Zm12 0v-3.25q0-3.25 3.325-5.25t8.675-2q5.4 0 8.7 2 3.3 2 3.3 5.25V36Zm27 0v-3.25q0-1-.175-1.875t-.575-1.725q.55-.1 1.1-.125Q39.9 29 40.5 29q3.4 0 5.45 1.2Q48 31.4 48 33.35V36Zm-15-7.5q-4 0-6.5 1.2T15 32.75V33h18v-.3q0-1.8-2.475-3T24 28.5Zm-16.5-1q-1.45 0-2.475-1.025Q4 25.45 4 24q0-1.45 1.025-2.475Q6.05 20.5 7.5 20.5q1.45 0 2.475 1.025Q11 22.55 11 24q0 1.45-1.025 2.475Q8.95 27.5 7.5 27.5Zm33 0q-1.45 0-2.475-1.025Q37 25.45 37 24q0-1.45 1.025-2.475Q39.05 20.5 40.5 20.5q1.45 0 2.475 1.025Q44 22.55 44 24q0 1.45-1.025 2.475Q41.95 27.5 40.5 27.5ZM24 24q-2.5 0-4.25-1.75T18 18q0-2.55 1.75-4.275Q21.5 12 24 12q2.55 0 4.275 1.725Q30 15.45 30 18q0 2.5-1.725 4.25T24 24Zm0-9q-1.25 0-2.125.85T21 18q0 1.25.875 2.125T24 21q1.3 0 2.15-.875Q27 19.25 27 18q0-1.3-.85-2.15Q25.3 15 24 15Zm0 18Zm0-15Z"/></svg>`, + name: 'groups' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/home.ts b/frontend/projects/admin/src/app/svg/home.ts new file mode 100644 index 0000000000..c9b93d6f01 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/home.ts @@ -0,0 +1,4 @@ +export const homeIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M11 39h7.5V26.5h11V39H37V19.5L24 9.75 11 19.5Zm-3 3V18L24 6l16 12v24H26.5V29.5h-5V42Zm16-17.65Z"/></svg>`, + name: 'home' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/hourglass-top.ts b/frontend/projects/admin/src/app/svg/hourglass-top.ts new file mode 100644 index 0000000000..5ce9c79df4 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/hourglass-top.ts @@ -0,0 +1,4 @@ +export const hourglassTopIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M316-140h328v-127q0-70-47.5-120.5T480-438q-69 0-116.5 50.5T316-267v127ZM160-80v-60h96v-127q0-70 36.5-128.5T394-480q-65-26-101.5-85T256-694v-126h-96v-60h640v60h-96v126q0 70-36.5 129T566-480q65 26 101.5 84.5T704-267v127h96v60H160Z"/></svg>`, + name: 'hourglass-top' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/info.ts b/frontend/projects/admin/src/app/svg/info.ts new file mode 100644 index 0000000000..a6ed8645d5 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/info.ts @@ -0,0 +1,4 @@ +export const infoIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M453-280h60v-240h-60v240Zm26.982-314q14.018 0 23.518-9.2T513-626q0-14.45-9.482-24.225-9.483-9.775-23.5-9.775-14.018 0-23.518 9.775T447-626q0 13.6 9.482 22.8 9.483 9.2 23.5 9.2Zm.284 514q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80-397.681 80-480.5q0-82.819 31.5-155.659Q143-709 197.5-763t127.341-85.5Q397.681-880 480.5-880q82.819 0 155.659 31.5Q709-817 763-763t85.5 127Q880-563 880-480.266q0 82.734-31.5 155.5T763-197.684q-54 54.316-127 86Q563-80 480.266-80Zm.234-60Q622-140 721-239.5t99-241Q820-622 721.188-721 622.375-820 480-820q-141 0-240.5 98.812Q140-622.375 140-480q0 141 99.5 240.5t241 99.5Zm-.5-340Z"/></svg>`, + name: 'info' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/insights.ts b/frontend/projects/admin/src/app/svg/insights.ts new file mode 100644 index 0000000000..02bc9ba3c9 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/insights.ts @@ -0,0 +1,4 @@ +export const insightsIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg>`, + name: 'insights' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/key.ts b/frontend/projects/admin/src/app/svg/key.ts new file mode 100644 index 0000000000..21b3337ae0 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/key.ts @@ -0,0 +1,4 @@ +export const keyIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M280 644q-28 0-48-20t-20-48q0-28 20-48t48-20q28 0 48 20t20 48q0 28-20 48t-48 20Zm0 172q-100 0-170-70T40 576q0-100 70-170t170-70q72 0 126 34t85 103h356l113 113-167 153-88-64-88 64-75-60h-51q-25 60-78.5 98.5T280 816Zm0-60q58 0 107-38.5t63-98.5h114l54 45 88-63 82 62 85-79-51-51H450q-12-56-60-96.5T280 396q-75 0-127.5 52.5T100 576q0 75 52.5 127.5T280 756Z"/></svg>`, + name: 'key' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/lock-open.ts b/frontend/projects/admin/src/app/svg/lock-open.ts new file mode 100644 index 0000000000..07ca2a1ba0 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/lock-open.ts @@ -0,0 +1,4 @@ +export const lockOpenIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M220-634h390v-96q0-54.167-37.882-92.083-37.883-37.917-92-37.917Q426-860 388-822.083 350-784.167 350-730h-60q0-79 55.606-134.5t134.5-55.5Q559-920 614.5-864.425T670-730v96h70q24.75 0 42.375 17.625T800-574v434q0 24.75-17.625 42.375T740-80H220q-24.75 0-42.375-17.625T160-140v-434q0-24.75 17.625-42.375T220-634Zm0 494h520v-434H220v434Zm260.168-140Q512-280 534.5-302.031T557-355q0-30-22.668-54.5t-54.5-24.5Q448-434 425.5-409.5t-22.5 55q0 30.5 22.668 52.5t54.5 22ZM220-140v-434 434Z"/></svg>`, + name: 'lock-open' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/lock.ts b/frontend/projects/admin/src/app/svg/lock.ts new file mode 100644 index 0000000000..9359492e22 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/lock.ts @@ -0,0 +1,4 @@ +export const lockIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M220-80q-24.75 0-42.375-17.625T160-140v-434q0-24.75 17.625-42.375T220-634h70v-96q0-78.85 55.606-134.425Q401.212-920 480.106-920T614.5-864.425Q670-808.85 670-730v96h70q24.75 0 42.375 17.625T800-574v434q0 24.75-17.625 42.375T740-80H220Zm0-60h520v-434H220v434Zm260.168-140Q512-280 534.5-302.031T557-355q0-30-22.668-54.5t-54.5-24.5Q448-434 425.5-409.5t-22.5 55q0 30.5 22.668 52.5t54.5 22ZM350-634h260v-96q0-54.167-37.882-92.083-37.883-37.917-92-37.917Q426-860 388-822.083 350-784.167 350-730v96ZM220-140v-434 434Z"/></svg>`, + name: 'lock' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/organization.ts b/frontend/projects/admin/src/app/svg/organization.ts new file mode 100644 index 0000000000..1d137cacce --- /dev/null +++ b/frontend/projects/admin/src/app/svg/organization.ts @@ -0,0 +1,4 @@ +export const organizationIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M4 42V6h19.5v8.25H44V42Zm3-3h5.25v-5.25H7Zm0-8.25h5.25V25.5H7Zm0-8.25h5.25v-5.25H7Zm0-8.25h5.25V9H7ZM15.25 39h5.25v-5.25h-5.25Zm0-8.25h5.25V25.5h-5.25Zm0-8.25h5.25v-5.25h-5.25Zm0-8.25h5.25V9h-5.25ZM23.5 39H41V17.25H23.5v5.25h4v3h-4v5.25h4v3h-4Zm9.25-13.5v-3h3v3Zm0 8.25v-3h3v3Z"/></svg>`, + name: 'organization' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/outgoing-mail.ts b/frontend/projects/admin/src/app/svg/outgoing-mail.ts new file mode 100644 index 0000000000..58d930fca3 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/outgoing-mail.ts @@ -0,0 +1,4 @@ +export const outgoingMailIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m734-164-42-42 73-74H584v-60h181l-73-74 42-42 146 146-146 146ZM140-280q-24.75 0-42.375-17.625T80-340v-440q0-24.75 17.625-42.375T140-840h560q24.75 0 42.375 17.625T760-780v232q-7.5-1-15-1.5t-15-.5q-8 0-15 .5t-15 1.5v-185L416-532 140-732v392h351q-1 8-1 13.5v13.75q0 8.25.5 16.5T493-280H140Zm36-500 240 174 246-174H176Zm-36 440v-440 440Z"/></svg>`, + name: 'outgoing-mail' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/payments.ts b/frontend/projects/admin/src/app/svg/payments.ts new file mode 100644 index 0000000000..a3efc9f14f --- /dev/null +++ b/frontend/projects/admin/src/app/svg/payments.ts @@ -0,0 +1,4 @@ +export const paymentsIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M540 636q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35ZM220 776q-24.75 0-42.375-17.625T160 716V316q0-24.75 17.625-42.375T220 256h640q24.75 0 42.375 17.625T920 316v400q0 24.75-17.625 42.375T860 776H220Zm100-60h440q0-42 29-71t71-29V416q-42 0-71-29t-29-71H320q0 42-29 71t-71 29v200q42 0 71 29t29 71Zm480 180H100q-24.75 0-42.375-17.625T40 836V376h60v460h700v60ZM220 716V316v400Z"/></svg>`, + name: 'payments' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/percent.ts b/frontend/projects/admin/src/app/svg/percent.ts new file mode 100644 index 0000000000..05f6fb1a41 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/percent.ts @@ -0,0 +1,4 @@ +export const percentIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M289.899-540Q236-540 198-578.101t-38-92Q160-724 198.101-762t92-38Q344-800 382-761.899t38 92Q420-616 381.899-578t-92 38Zm-.017-60Q319-600 339.5-620.382q20.5-20.383 20.5-49.5Q360-699 339.618-719.5q-20.383-20.5-49.5-20.5Q261-740 240.5-719.618q-20.5 20.383-20.5 49.5Q220-641 240.382-620.5q20.383 20.5 49.5 20.5Zm380.017 440Q616-160 578-198.101t-38-92Q540-344 578.101-382t92-38Q724-420 762-381.899t38 92Q800-236 761.899-198t-92 38Zm-.017-60Q699-220 719.5-240.382q20.5-20.383 20.5-49.5Q740-319 719.618-339.5q-20.383-20.5-49.5-20.5Q641-360 620.5-339.618q-20.5 20.383-20.5 49.5Q600-261 620.382-240.5q20.383 20.5 49.5 20.5ZM202-160l-42-42 598-598 42 42-598 598Z"/></svg>`, + name: 'percent' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/piggy-bank.ts b/frontend/projects/admin/src/app/svg/piggy-bank.ts new file mode 100644 index 0000000000..cd15d15ef1 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/piggy-bank.ts @@ -0,0 +1,4 @@ +export const piggyBankIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M640-520q17 0 28.5-11.5T680-560q0-17-11.5-28.5T640-600q-17 0-28.5 11.5T600-560q0 17 11.5 28.5T640-520ZM320-620h200v-60H320v60ZM180-120q-34-114-67-227.5T80-580q0-92 64-156t156-64h200q29-38 70.5-59t89.5-21q25 0 42.5 17.5T720-820q0 6-1.5 12t-3.5 11q-4 11-7.5 22.5T702-751l91 91h87v279l-113 37-67 224H480v-80h-80v80H180Zm45-60h115v-80h200v80h115l63-210 102-35v-175h-52L640-728q1-25 6.5-48.5T658-824q-38 10-72 29.5T534-740H300q-66.286 0-113.143 46.857T140-580q0 103.158 29 201.579T225-180Zm255-322Z"/></svg>`, + name: 'piggy-bank' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/qr-code.ts b/frontend/projects/admin/src/app/svg/qr-code.ts new file mode 100644 index 0000000000..31ef01a211 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/qr-code.ts @@ -0,0 +1,4 @@ +export const qrCodeIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M520-120v-80h80v80h-80Zm-80-80v-200h80v200h-80Zm320-120v-160h80v160h-80Zm-80-160v-80h80v80h-80Zm-480 80v-80h80v80h-80Zm-80-80v-80h80v80h-80Zm360-280v-80h80v80h-80ZM170-650h140v-140H170v140Zm-50 50v-240h240v240H120Zm50 430h140v-140H170v140Zm-50 50v-240h240v240H120Zm530-530h140v-140H650v140Zm-50 50v-240h240v240H600Zm80 480v-120h-80v-80h160v120h80v80H680ZM520-400v-80h160v80H520Zm-160 0v-80h-80v-80h240v80h-80v80h-80Zm40-200v-160h80v80h80v80H400Zm-190-90v-60h60v60h-60Zm0 480v-60h60v60h-60Zm480-480v-60h60v60h-60Z"/></svg>`, + name: 'qr-code' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/reset.ts b/frontend/projects/admin/src/app/svg/reset.ts new file mode 100644 index 0000000000..5a77218e49 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/reset.ts @@ -0,0 +1,4 @@ +export const resetIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M132 896v-60h123l-14-14q-60-60-86.5-121.5T128 580q0-109 62.5-195T355 266v62q-76 30-121.5 99T188 580q0 52 17.5 98.5T265 763l30 27V673h60v223H132Zm699-334h-60q-2-51-20-94.5T695 389l-30-27v117h-60V256h223v60H704l15 14q58 55 84 115t28 117Zm-135 454-5-48q-20-6-41-17.5T616 925l-42 20-35-54 38-30q-5-23-5-41.5t5-41.5l-38-30 35-55 42 20q13-12 34-24t41-18l5-49h60l6 49q20 6 41 18t34 24l42-20 35 55-38 30q5 23 5 41.5t-5 41.5l38 30-35 54-42-20q-13 14-34 25.5T762 968l-6 48h-60Zm30-95q44 0 73-29t29-73q0-44-29-73t-73-29q-44 0-73 29t-29 73q0 44 29 73t73 29Z"/></svg>`, + name: 'reset' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/search.ts b/frontend/projects/admin/src/app/svg/search.ts new file mode 100644 index 0000000000..4c625f88bd --- /dev/null +++ b/frontend/projects/admin/src/app/svg/search.ts @@ -0,0 +1,4 @@ +export const searchIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M796-121 533-384q-30 26-69.959 40.5T378-329q-108.162 0-183.081-75Q120-479 120-585t75-181q75-75 181.5-75t181 75Q632-691 632-584.85 632-542 618-502q-14 40-42 75l264 262-44 44ZM377-389q81.25 0 138.125-57.5T572-585q0-81-56.875-138.5T377-781q-82.083 0-139.542 57.5Q180-666 180-585t57.458 138.5Q294.917-389 377-389Z"/></svg>`, + name: 'search' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/settings.ts b/frontend/projects/admin/src/app/svg/settings.ts new file mode 100644 index 0000000000..aa6c7b9c04 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/settings.ts @@ -0,0 +1,4 @@ +export const settingsIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="m19.4 44-1-6.3q-.95-.35-2-.95t-1.85-1.25l-5.9 2.7L4 30l5.4-3.95q-.1-.45-.125-1.025Q9.25 24.45 9.25 24q0-.45.025-1.025T9.4 21.95L4 18l4.65-8.2 5.9 2.7q.8-.65 1.85-1.25t2-.9l1-6.35h9.2l1 6.3q.95.35 2.025.925Q32.7 11.8 33.45 12.5l5.9-2.7L44 18l-5.4 3.85q.1.5.125 1.075.025.575.025 1.075t-.025 1.05q-.025.55-.125 1.05L44 30l-4.65 8.2-5.9-2.7q-.8.65-1.825 1.275-1.025.625-2.025.925l-1 6.3ZM24 30.5q2.7 0 4.6-1.9 1.9-1.9 1.9-4.6 0-2.7-1.9-4.6-1.9-1.9-4.6-1.9-2.7 0-4.6 1.9-1.9 1.9-1.9 4.6 0 2.7 1.9 4.6 1.9 1.9 4.6 1.9Zm0-3q-1.45 0-2.475-1.025Q20.5 25.45 20.5 24q0-1.45 1.025-2.475Q22.55 20.5 24 20.5q1.45 0 2.475 1.025Q27.5 22.55 27.5 24q0 1.45-1.025 2.475Q25.45 27.5 24 27.5Zm0-3.5Zm-2.2 17h4.4l.7-5.6q1.65-.4 3.125-1.25T32.7 32.1l5.3 2.3 2-3.6-4.7-3.45q.2-.85.325-1.675.125-.825.125-1.675 0-.85-.1-1.675-.1-.825-.35-1.675L40 17.2l-2-3.6-5.3 2.3q-1.15-1.3-2.6-2.175-1.45-.875-3.2-1.125L26.2 7h-4.4l-.7 5.6q-1.7.35-3.175 1.2-1.475.85-2.625 2.1L10 13.6l-2 3.6 4.7 3.45q-.2.85-.325 1.675-.125.825-.125 1.675 0 .85.125 1.675.125.825.325 1.675L8 30.8l2 3.6 5.3-2.3q1.2 1.2 2.675 2.05Q19.45 35 21.1 35.4Z"/></svg>`, + name: 'settings' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/subscription.ts b/frontend/projects/admin/src/app/svg/subscription.ts new file mode 100644 index 0000000000..1ddf448bfc --- /dev/null +++ b/frontend/projects/admin/src/app/svg/subscription.ts @@ -0,0 +1,4 @@ +export const subscriptionIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M7 25.85V31h34v-5.15ZM7 4h34q1.25 0 2.125.875T44 7v24q0 1.25-.875 2.125T41 34h-9.7v10L24 40.3 16.7 44V34H7q-1.25 0-2.125-.875T4 31V7q0-1.25.875-2.125T7 4Zm0 16.45h34V7H7ZM7 31V7v24Z"/></svg>`, + name: 'subscription' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/ticket-reserved.ts b/frontend/projects/admin/src/app/svg/ticket-reserved.ts new file mode 100644 index 0000000000..1bf2e5876f --- /dev/null +++ b/frontend/projects/admin/src/app/svg/ticket-reserved.ts @@ -0,0 +1,4 @@ +export const ticketReservedIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m376-318 104-81 104 81-40-128 98-82H522l-42-122-42 122H318l98 82-40 128ZM140-160q-25 0-42.5-17.5T80-220v-132q0-10 5.5-17.5T100-380q31-11 48.5-39.5T166-480q0-31-17.5-60T100-580q-9-3-14.5-10.5T80-608v-132q0-25 17.5-42.5T140-800h680q25 0 42.5 17.5T880-740v132q0 10-5.5 17.5T860-580q-31 11-48.5 40T794-480q0 32 17.5 60.5T860-380q9 3 14.5 10.5T880-352v132q0 25-17.5 42.5T820-160H140Zm0-60h680v-109q-38-26-62-65t-24-86q0-47 24-86t62-65v-109H140v109q39 26 62.5 65t23.5 86q0 47-23.5 86T140-329v109Zm340-260Z"/></svg>`, + name: 'ticket-reserved' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/ticket.ts b/frontend/projects/admin/src/app/svg/ticket.ts new file mode 100644 index 0000000000..c53182ab43 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/ticket.ts @@ -0,0 +1,4 @@ +export const ticketIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M480 773q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm0-167q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm0-167q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm340 457H140q-24.75 0-42.375-17.625T80 836V683q37-8 61.5-37.5T166 576q0-40-24.5-70T80 469V316q0-24.75 17.625-42.375T140 256h680q24.75 0 42.375 17.625T880 316v153q-37 7-61.5 37T794 576q0 40 24.5 69.5T880 683v153q0 24.75-17.625 42.375T820 896Zm0-60V727q-38-26-62-65t-24-86q0-47 24-86t62-65V316H140v109q39 26 62.5 65t23.5 86q0 47-23.5 86T140 727v109h680ZM480 576Z"/></svg>`, + name: 'ticket' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/visibility-on.ts b/frontend/projects/admin/src/app/svg/visibility-on.ts new file mode 100644 index 0000000000..c044e4bc7b --- /dev/null +++ b/frontend/projects/admin/src/app/svg/visibility-on.ts @@ -0,0 +1,4 @@ +export const visibilityOnIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="M480.118 726Q551 726 600.5 676.382q49.5-49.617 49.5-120.5Q650 485 600.382 435.5q-49.617-49.5-120.5-49.5Q409 386 359.5 435.618q-49.5 49.617-49.5 120.5Q310 627 359.618 676.5q49.617 49.5 120.5 49.5Zm-.353-58Q433 668 400.5 635.265q-32.5-32.736-32.5-79.5Q368 509 400.735 476.5q32.736-32.5 79.5-32.5Q527 444 559.5 476.735q32.5 32.736 32.5 79.5Q592 603 559.265 635.5q-32.736 32.5-79.5 32.5ZM480 856q-146 0-264-83T40 556q58-134 176-217t264-83q146 0 264 83t176 217q-58 134-176 217t-264 83Zm0-300Zm-.169 240Q601 796 702.5 730.5 804 665 857 556q-53-109-154.331-174.5-101.332-65.5-222.5-65.5Q359 316 257.5 381.5 156 447 102 556q54 109 155.331 174.5 101.332 65.5 222.5 65.5Z"/></svg>`, + name: 'visibility-on' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/visibilityoff.ts b/frontend/projects/admin/src/app/svg/visibilityoff.ts new file mode 100644 index 0000000000..e7ed5863a7 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/visibilityoff.ts @@ -0,0 +1,4 @@ +export const visibilityoffIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960"><path d="m629 637-44-44q26-71-27-118t-115-24l-44-44q17-11 38-16t43-5q71 0 120.5 49.5T650 556q0 22-5.5 43.5T629 637Zm129 129-40-40q49-36 85.5-80.5T857 556q-50-111-150-175.5T490 316q-42 0-86 8t-69 19l-46-47q35-16 89.5-28T485 256q143 0 261.5 81.5T920 556q-26 64-67 117t-95 93Zm58 226L648 827q-35 14-79 21.5t-89 7.5q-146 0-265-81.5T40 556q20-52 55.5-101.5T182 360L56 234l42-43 757 757-39 44ZM223 402q-37 27-71.5 71T102 556q51 111 153.5 175.5T488 796q33 0 65-4t48-12l-64-64q-11 5-27 7.5t-30 2.5q-70 0-120-49t-50-121q0-15 2.5-30t7.5-27l-97-97Zm305 142Zm-116 58Z"/></svg>`, + name: 'visibilityoff' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/waiting-list.ts b/frontend/projects/admin/src/app/svg/waiting-list.ts new file mode 100644 index 0000000000..bdc6d7b78e --- /dev/null +++ b/frontend/projects/admin/src/app/svg/waiting-list.ts @@ -0,0 +1,4 @@ +export const waitingListIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M120-160v-60h480v60H120Zm519.894-290Q561-450 505.5-505.606t-55.5-134.5Q450-719 505.606-774.5t134.5-55.5Q719-830 774.5-774.394t55.5 134.5Q830-561 774.394-505.5t-134.5 55.5ZM120-500v-60h262q5.32 16.323 12.16 31.161Q401-514 409-500H120Zm0 170v-60h419q13.8 6.364 29.4 10.682Q584-375 600-373v43H120Zm500-190h40v-160h-40v160Zm20-200q8 0 14-6t6-14q0-8-6-14t-14-6q-8 0-14 6t-6 14q0 8 6 14t14 6Z"/></svg>`, + name: 'waiting-list' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/svg/warning.ts b/frontend/projects/admin/src/app/svg/warning.ts new file mode 100644 index 0000000000..a978066122 --- /dev/null +++ b/frontend/projects/admin/src/app/svg/warning.ts @@ -0,0 +1,4 @@ +export const warningIcon = { + data: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m40-120 440-760 440 760H40Zm104-60h672L480-760 144-180Zm340.175-57q12.825 0 21.325-8.675 8.5-8.676 8.5-21.5 0-12.825-8.675-21.325-8.676-8.5-21.5-8.5-12.825 0-21.325 8.675-8.5 8.676-8.5 21.5 0 12.825 8.675 21.325 8.676 8.5 21.5 8.5ZM454-348h60v-224h-60v224Zm26-122Z"/></svg>`, + name: 'warning' as const +}; \ No newline at end of file diff --git a/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.html b/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.html new file mode 100644 index 0000000000..4457c39728 --- /dev/null +++ b/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.html @@ -0,0 +1,96 @@ +<div class="container"> + <h1 translate="admin.user.new.title"></h1> + <form [formGroup]="userForm" (ngSubmit)="save()"> + <div class="form-group my-3"> + <label + for="UserOrganizationId" + translate="admin.user.new.organization" + ></label> + <select + id="UserOrganizationId" + class="form-select form-control" + formControlName="organizationId" + > + <option *ngFor="let org of organizations$ | async" [ngValue]="org.id"> + {{ org.name }} + </option> + </select> + </div> + <div class="form-group my-3"> + <label for="userRole" translate="admin.user.new.role"></label> + <select id="userRole" class="form-select" formControlName="role"> + <option *ngFor="let role of roles$ | async" [ngValue]="role.role"> + {{ role.description }} + </option> + </select> + </div> + <div class="form-group my-3"> + <label for="userName" translate="admin.common.username"></label> + <input + id="userName" + type="text" + class="form-control" + formControlName="username" + [class.is-invalid]=" + userName?.invalid && (userName?.dirty || userName?.touched) + " + /> + </div> + <div class="form-group my-3"> + <label for="userFirstName" translate="common.first-name"></label> + <input + id="userFirstName" + type="text" + class="form-control" + formControlName="firstName" + [class.is-invalid]=" + userFirstName?.invalid && + (userFirstName?.dirty || userFirstName?.touched) + " + /> + </div> + <div class="form-group my-3"> + <label for="userLastName" translate="common.last-name"></label> + <input + id="userLastName" + type="text" + class="form-control" + formControlName="lastName" + [class.is-invalid]=" + userLastName?.invalid && + (userLastName?.dirty || userLastName?.touched) + " + /> + </div> + + <div class="form-group my-3"> + <label for="userMail" translate="common.email"></label> + <input + id="userMail" + type="email" + class="form-control" + formControlName="emailAddress" + [class.is-invalid]=" + userMail?.invalid && (userMail?.dirty || userMail?.touched) + " + /> + </div> + <div class="d-flex justify-content-between"> + <div> + <button + class="btn btn-lg btn-outline-secondary" + [routerLink]="['/access-control/users']" + translate="admin.common.cancel" + ></button> + </div> + <div> + <button + type="submit" + class="btn btn-lg btn-primary" + [disabled]="userForm.invalid" + translate="admin.common.save" + ></button> + </div> + </div> + </form> +</div> diff --git a/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.scss b/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.ts b/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.ts new file mode 100644 index 0000000000..377af17ac2 --- /dev/null +++ b/frontend/projects/admin/src/app/user-system-edit/user-system-edit.component.ts @@ -0,0 +1,92 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { OrganizationService } from '../shared/organization.service'; +import { UserService } from '../shared/user.service'; +import { Observable, map, of } from 'rxjs'; +import { Organization } from '../model/organization'; +import { Role } from '../model/role'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RoleType, User } from '../model/user'; + +@Component({ + selector: 'app-user-system-edit', + templateUrl: './user-system-edit.component.html', + styleUrls: ['./user-system-edit.component.scss'], +}) +export class UserSystemEditComponent implements OnInit { + public userId: string | null = null; + public userForm: FormGroup; + public organizations$?: Observable<Organization[]>; + public roles$?: Observable<Role[]>; + public editMode: boolean | undefined; + public user$: Observable<User | null> = of(); + constructor( + private readonly route: ActivatedRoute, + formBuilder: FormBuilder, + private readonly organizationService: OrganizationService, + private readonly userService: UserService, + private readonly router: Router + ) { + this.userForm = formBuilder.group({ + target: null, + id: [null], + organizationId: [null, Validators.required], + role: [null, Validators.required], + username: [null, Validators.required], + firstName: [null, Validators.required], + lastName: [null, Validators.required], + emailAddress: [null, Validators.required], + }); + } + + get userName() { + return this.userForm.get('username'); + } + get userLastName() { + return this.userForm.get('lastName'); + } + get userFirstName() { + return this.userForm.get('firstName'); + } + + get userMail() { + return this.userForm.get('emailAddress'); + } + + ngOnInit(): void { + this.userId = this.route.snapshot.paramMap.get('userId'); + + this.organizations$ = this.organizationService.getOrganizations(); + this.roles$ = this.userService.getAllRoles().pipe( + map((roles) => { + return roles.filter((role) => role.target.includes('USER')); + }) + ); + + if (this.userId !== null) { + this.editMode = true; + this.user$ = this.userService.getUser(this.userId); + this.user$.subscribe((user) => { + if (user) this.userForm.patchValue(user); + }); + } else { + this.editMode = false; + } + } + + save(): void { + let result: Observable<any>; + if (this.editMode) { + result = this.userService.update(this.userForm.value); + } else { + result = this.userService.create(this.userForm.value); + } + result.subscribe((res) => { + if (res === 'OK') { + this.router.navigate(['/access-control/users']); + } + }); + } + + +} diff --git a/frontend/projects/admin/src/app/user-system/user-system.component.html b/frontend/projects/admin/src/app/user-system/user-system.component.html new file mode 100644 index 0000000000..cab790a3c1 --- /dev/null +++ b/frontend/projects/admin/src/app/user-system/user-system.component.html @@ -0,0 +1,107 @@ +<div class="container"> + <h1 class="my-3" translate="admin.user.list.title"></h1> + <hr /> + <div class="d-flex py-3"> + <select + id="UserOrganizationId" + class="form-select form-control d-flex me-3" + [(ngModel)]="selectedOrganization" + > + <option></option> + <option *ngFor="let org of organizations$ | async" [ngValue]="org"> + {{ org.name }} + </option> + </select> + <button + type="button" + class="btn btn-success text-nowrap" + [routerLink]="['/access-control/users/new']" + > + <svg-icon key="add" size="lg"></svg-icon> + <span translate="admin.common.add-new"></span> + </button> + </div> +</div> + +<div class="container"> + <table class="table table-striped"> + <thead> + <tr> + <th scope="col" translate="admin.user.list.enabled"></th> + <th scope="col" translate="admin.common.username"></th> + <th scope="col" translate="admin.common.name"></th> + <th scope="col" translate="admin.user.list.role"></th> + <th scope="col" translate="admin.user.list.member-of"></th> + <th scope="col" translate="common.edit"></th> + <th scope="col" translate="admin.user.list.reset-password"></th> + <th scope="col"> + <span translate="admin.user.list.enabled"></span>/<span + translate="admin.user.list.disable" + ></span> + </th> + <th scope="col" translate="admin.common.delete"></th> + </tr> + </thead> + <tbody> + <tr + *ngFor=" + let user of users$ | async | usersFilterOrg : selectedOrganization + " + > + <td>{{ user.enabled }}</td> + <td scope="row"> + {{ user.username }} + </td> + <td>{{ user.firstName }} {{ user.lastName }}</td> + <td> + <span *ngFor="let role of user.roles">{{ + roleDescription(role) | async + }}</span> + </td> + <td> + <span *ngFor="let org of user.memberOf" class="badge bg-primary">{{ + org.name + }}</span> + </td> + <td> + <!-- E D I T --> + <a + [routerLink]="['/access-control/users', user.id, 'edit']" + class="btn" + *ngIf="user.enabled" + ><svg-icon key="edit" size="lg"></svg-icon + ></a> + </td> + <td> + <button class="btn"> + <svg-icon key="reset"></svg-icon> + </button> + </td> + <td> + <button + *ngIf="user.enabled" + class="btn btn-warning" + (click)="enable(user)" + > + <svg-icon key="visibilityoff" size="lg"></svg-icon> + <span translate="admin.user.list.disable"></span> + </button> + <button + *ngIf="!user.enabled" + class="btn btn-success" + (click)="enable(user)" + > + <svg-icon key="visibility-on" size="lg"></svg-icon> + <span translate="admin.user.list.enabled"></span> + </button> + </td> + <td> + <button class="btn btn-danger text-nowrap" (click)="deleteUser(user)"> + <svg-icon key="delete" size="lg"></svg-icon> + <span translate="admin.common.delete"></span> + </button> + </td> + </tr> + </tbody> + </table> +</div> diff --git a/frontend/projects/admin/src/app/user-system/user-system.component.scss b/frontend/projects/admin/src/app/user-system/user-system.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/admin/src/app/user-system/user-system.component.ts b/frontend/projects/admin/src/app/user-system/user-system.component.ts new file mode 100644 index 0000000000..9708bcac2d --- /dev/null +++ b/frontend/projects/admin/src/app/user-system/user-system.component.ts @@ -0,0 +1,70 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable, map, of, share, shareReplay } from 'rxjs'; +import { UserService } from '../shared/user.service'; +import { RoleType, User, UserType } from '../model/user'; +import { Role } from '../model/role'; +import { Organization } from '../model/organization'; + +@Component({ + selector: 'app-user-system', + templateUrl: './user-system.component.html', + styleUrls: ['./user-system.component.scss'], +}) +export class UserSystemComponent implements OnInit { + public users$?: Observable<User[]>; + public roles$: Observable<Map<RoleType, Role>> = of(); + public organizations$?: Observable<Organization[]>; + public selectedOrganization?: Organization; + + constructor(private readonly userService: UserService) {} + + ngOnInit(): void { + this.loadUsers(); + this.roles$ = this.userService.getAllRoles().pipe( + map((roles) => { + const map: Map<RoleType, Role> = new Map(); + roles.forEach((role) => { + map.set(role.role, role); + }); + return map; + }), + shareReplay(1) + ); + } + + private loadUsers(){ + this.users$ = this.userService.getAllUsers(); + this.users$.subscribe((users) => { + const orgIdToOrg = new Map<number, Organization>(); + users.forEach((user) => { + user.memberOf.forEach((org) => { + orgIdToOrg.set(org.id, org); + }); + }); + + const organizations = Array.from(orgIdToOrg.values()); + this.organizations$ = of(organizations); + }); + + } + + enable(user: User) { + this.userService.enable(user, !user.enabled).subscribe((result) => { + this.users$ = this.userService.getAllUsers(); + }); + } + + deleteUser(user: User) { + if ( + window.confirm(`The user ${user.username} will be deleted. Are you sure?`) + ) { + this.userService.deleteUser(user).subscribe((result) => { + this.users$ = this.userService.getAllUsers(); + }); + } + } + + roleDescription(role: RoleType): Observable<string | undefined> { + return this.roles$.pipe(map((roles) => roles.get(role)?.description)); + } +} diff --git a/frontend/projects/admin/src/assets/alfio-logo.svg b/frontend/projects/admin/src/assets/alfio-logo.svg new file mode 100644 index 0000000000..cdd73fd48d --- /dev/null +++ b/frontend/projects/admin/src/assets/alfio-logo.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.w3.org/2000/svg" + id="svg8" + version="1.1" + viewBox="0 0 42.333332 21.166667" + height="80" + width="160"> + <defs + id="defs2" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(0,-275.83332)" + id="layer1"> + <g + transform="translate(0,3.7041383)" + id="g1477"> + <path + id="path911" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;line-height:1.25;font-family:'Architects Daughter';-inkscape-font-specification:'Architects Daughter';letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:0.94117647;stroke:none;stroke-width:0.26458332" + d="m 11.129037,283.39877 h 0.463021 q 0.28112,0 0.529166,0.0496 0.264584,0.0496 0.264584,0.3638 0.115755,0.66146 -0.777214,0.64492 -0.248047,-0.0165 -0.463021,-0.0331 -0.198437,-0.0165 -0.330729,0 -0.132291,0 -0.115755,0.0827 0.165365,0.47956 0.347266,1.07487 0.198437,0.59531 0.429948,1.19062 0.595312,1.47175 1.124479,2.11667 -0.28112,0.34727 -0.661459,0.57878 -0.363802,0.23151 -0.396875,0.23151 -0.01654,0 -0.03307,-0.0165 l -2.331641,-4.91133 -4.5144529,1.10795 -0.1653645,0.69453 q -0.4795573,1.90169 -1.0748698,2.64583 -0.099219,0.16536 -0.4134114,0.0331 -0.5291667,-0.19844 -0.5291667,-0.33073 0,-0.0331 0.3472657,-0.95911 0.3472656,-0.92604 0.6945312,-1.86862 0.363802,-0.95912 0.7441406,-2.00091 0.3968749,-1.0418 0.7441405,-1.98438 0.3472656,-0.94258 0.6283854,-1.68672 0.2811198,-0.76067 0.396875,-1.0914 0.1157552,-0.34727 0.1488281,-0.59531 0.049609,-0.26459 0.1322917,-0.44649 0.1653646,-0.38034 0.8268228,-0.38034 0.7110677,0.39688 1.0914062,1.0418 0.396875,0.62838 0.6614583,1.35599 0.2811198,0.7276 0.5457031,1.47174 0.2645834,0.74414 0.7110677,1.35599 0.148828,0.23151 0.446484,0.24805 0.297657,0.0165 0.529167,0.0165 z m -4.1341144,-3.57188 q -0.1322916,1.00873 -0.5953124,2.06706 -0.4630208,1.05833 -0.7441406,1.65364 -0.2811198,0.57878 -0.4795573,1.07487 1.1244791,0 2.7615884,-0.66145 0.4134114,-0.1819 0.79375,-0.33073 -0.7772135,-3.32383 -1.7363281,-3.80339 z" /> + <path + id="path913" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;line-height:1.25;font-family:'Architects Daughter';-inkscape-font-specification:'Architects Daughter';letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:0.94117647;stroke:none;stroke-width:0.26458332" + d="m 13.642578,283.03496 q -0.03307,-0.89296 -0.06615,-1.5875 -0.03307,-0.71106 -0.04961,-1.04179 0,-0.34727 -0.264583,-1.67018 0.148828,-0.19844 0.281119,-0.38034 0.148828,-0.1819 0.413412,-0.1819 0.578776,0 0.892968,4.87825 0.165365,2.82774 0.165365,5.65547 0,0.76068 -1.058333,0.76068 l -0.132292,-0.0165 q 0,-0.8599 -0.04961,-2.19935 -0.04961,-1.33945 -0.08268,-2.33164 -0.01654,-0.99219 -0.04961,-1.88516 z" /> + <path + id="path915" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;line-height:1.25;font-family:'Architects Daughter';-inkscape-font-specification:'Architects Daughter';letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:0.94117647;stroke:none;stroke-width:0.26458332" + d="m 25.515755,283.92793 q 0.08268,0 0.08268,0.44649 0,0.44648 0.03307,0.61185 l -5.672005,0.69453 0.297656,3.67109 -1.339453,-0.0661 q 0,-1.48829 -0.330729,-3.40651 l -2.149739,0.82682 q -0.214974,0 -0.363802,-0.24805 -0.264584,-0.42995 -0.264584,-0.77721 l 2.513542,-0.89297 q -0.330729,-2.84427 -0.330729,-4.61367 0,-1.78594 0.297656,-2.77813 0.297656,-0.99219 0.859896,-1.32291 0.578776,-0.34727 1.223698,-0.34727 0.644921,0 1.30638,0.28112 0.677995,0.28112 1.289843,0.71107 0.611849,0.42995 1.041797,0.97565 0.429948,0.52917 0.429948,1.0418 0,0.39687 -0.08268,0.56224 -0.661458,-0.92605 -1.604037,-1.65365 -1.124479,-0.8599 -2.116666,-0.8599 -0.942578,0 -1.256771,1.33946 -0.148828,0.69453 -0.148828,1.96784 0,1.25677 0.06615,2.3151 0.06615,1.0418 0.396875,2.10013 1.02526,0 2.298567,-0.1819 2.447396,-0.38034 3.522266,-0.39688 z" /> + <path + id="path917" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;line-height:1.25;font-family:'Architects Daughter';-inkscape-font-specification:'Architects Daughter';letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:0.94117647;stroke:none;stroke-width:0.26458332" + d="m 26.392187,289.46765 q -0.380339,-0.0331 -0.380339,-0.33073 0,-0.3638 0.413412,-0.678 0.165364,-0.11575 0.248047,-0.11575 0.09922,0 0.148828,0.0165 0.347265,0 0.463021,0.14883 0.132291,0.13229 0.132291,0.3638 0,0.23151 -0.09922,0.34727 -0.09922,0.11575 -0.248046,0.16536 -0.248047,0.0827 -0.677995,0.0827 z" /> + <path + id="path919" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;line-height:1.25;font-family:'Architects Daughter';-inkscape-font-specification:'Architects Daughter';letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:0.94117647;stroke:none;stroke-width:0.26458332" + d="m 29.947526,281.67897 q 0.214974,3.09232 0.363802,7.16029 -0.09922,0.51263 -0.79375,0.51263 -0.181901,0 -0.380339,-0.0661 -0.181901,-0.0827 -0.23151,-0.21498 v -7.3918 z m -1.537891,-2.24895 q 0,-0.57878 0.677995,-0.57878 0.446484,0 0.727604,0.19844 0.04961,0.1819 0.04961,0.3638 0,0.16536 -0.132291,0.33073 -0.132292,0.16536 -0.396875,0.16536 -0.264584,0 -0.529167,-0.11575 -0.264583,-0.13229 -0.396876,-0.3638 z" /> + <path + id="path921" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.93333244px;line-height:1.25;font-family:'Architects Daughter';-inkscape-font-specification:'Architects Daughter';letter-spacing:0px;word-spacing:0px;fill:#777777;fill-opacity:0.94117647;stroke:none;stroke-width:0.26458332" + d="m 31.452343,286.30918 q 0,-1.9513 1.686719,-3.14192 1.339453,-0.94258 3.224609,-1.14102 0.09922,0 0.463021,0 0.363802,0 0.975651,0.21497 1.355989,0.46303 1.852083,1.63711 0.198437,0.46302 0.198437,0.79375 0,0.3142 -0.01654,0.44649 0,2.06706 -1.570963,3.25768 -1.455209,1.09141 -3.638021,1.09141 -1.570964,0 -2.38125,-0.72761 -0.79375,-0.7276 -0.79375,-2.43086 z m 1.141016,0.39688 q 0,1.02526 0.545703,1.38906 0.413411,0.26458 1.389062,0.26458 0.975651,0 1.719792,-0.14882 0.74414,-0.14883 1.322916,-0.51263 1.240235,-0.79375 1.240235,-2.46394 0,-0.99218 -0.529167,-1.62057 -0.578776,-0.67799 -1.521354,-0.67799 -0.644922,0.11575 -1.30638,0.24804 -0.644922,0.13229 -1.223698,0.51263 -1.256771,0.81029 -1.637109,3.00964 z" /> + </g> + </g> +</svg> diff --git a/frontend/projects/admin/src/assets/svg/access.svg b/frontend/projects/admin/src/assets/svg/access.svg new file mode 100644 index 0000000000..e8a60e358f --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/access.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 44q-7-1.75-11.5-8.125T8 21.9V10l16-6 16 6v11.9q0 7.6-4.5 13.975T24 44Zm0-3.1q5.3-1.75 8.775-6.425Q36.25 29.8 36.85 24H24V7.25L11 12.1v9.8q0 .6.025 1.025.025.425.125 1.075H24Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/accountcircle.svg b/frontend/projects/admin/src/assets/svg/accountcircle.svg new file mode 100644 index 0000000000..9419ad7859 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/accountcircle.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M11.1 35.25q3.15-2.2 6.25-3.375Q20.45 30.7 24 30.7q3.55 0 6.675 1.175t6.275 3.375q2.2-2.7 3.125-5.45Q41 27.05 41 24q0-7.25-4.875-12.125T24 7q-7.25 0-12.125 4.875T7 24q0 3.05.95 5.8t3.15 5.45ZM24 25.5q-2.9 0-4.875-1.975T17.15 18.65q0-2.9 1.975-4.875T24 11.8q2.9 0 4.875 1.975t1.975 4.875q0 2.9-1.975 4.875T24 25.5ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 24q0-4.15 1.575-7.775t4.3-6.35q2.725-2.725 6.375-4.3Q19.9 4 24 4q4.15 0 7.775 1.575t6.35 4.3q2.725 2.725 4.3 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.3 6.375-2.725 2.725-6.35 4.3Q28.15 44 24 44Zm0-3q2.75 0 5.375-.8t5.175-2.8q-2.55-1.8-5.2-2.75-2.65-.95-5.35-.95-2.7 0-5.35.95-2.65.95-5.2 2.75 2.55 2 5.175 2.8Q21.25 41 24 41Zm0-18.5q1.7 0 2.775-1.075t1.075-2.775q0-1.7-1.075-2.775T24 14.8q-1.7 0-2.775 1.075T20.15 18.65q0 1.7 1.075 2.775T24 22.5Zm0-3.85Zm0 18.7Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/add-circle-new.svg b/frontend/projects/admin/src/assets/svg/add-circle-new.svg new file mode 100644 index 0000000000..0570982608 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/add-circle-new.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M453 776h60V610h167v-60H513V376h-60v174H280v60h173v166Zm27.266 200q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80 658.319 80 575.5q0-82.819 31.5-155.659Q143 347 197.5 293t127.341-85.5Q397.681 176 480.5 176q82.819 0 155.659 31.5Q709 239 763 293t85.5 127Q880 493 880 575.734q0 82.734-31.5 155.5T763 858.316q-54 54.316-127 86Q563 976 480.266 976Zm.234-60Q622 916 721 816.5t99-241Q820 434 721.188 335 622.375 236 480 236q-141 0-240.5 98.812Q140 433.625 140 576q0 141 99.5 240.5t241 99.5Zm-.5-340Z"/></svg> diff --git a/frontend/projects/admin/src/assets/svg/add.svg b/frontend/projects/admin/src/assets/svg/add.svg new file mode 100644 index 0000000000..76d4d8a219 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/add.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M22.5 38V25.5H10v-3h12.5V10h3v12.5H38v3H25.5V38Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/arrow-back.svg b/frontend/projects/admin/src/assets/svg/arrow-back.svg new file mode 100644 index 0000000000..5811847c7d --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/arrow-back.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M480-160 160-480l320-320 42 42-248 248h526v60H274l248 248-42 42Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/arrowdropdown.svg b/frontend/projects/admin/src/assets/svg/arrowdropdown.svg new file mode 100644 index 0000000000..b1bd72c0c9 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/arrowdropdown.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m24 30-10-9.95h20Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/calendar-event.svg b/frontend/projects/admin/src/assets/svg/calendar-event.svg new file mode 100644 index 0000000000..74cda1d15a --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/calendar-event.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M596.817-220Q556-220 528-248.183q-28-28.183-28-69T528.183-386q28.183-28 69-28T666-385.817q28 28.183 28 69T665.817-248q-28.183 28-69 28ZM180-80q-24 0-42-18t-18-42v-620q0-24 18-42t42-18h65v-60h65v60h340v-60h65v60h65q24 0 42 18t18 42v620q0 24-18 42t-42 18H180Zm0-60h600v-430H180v430Zm0-490h600v-130H180v130Zm0 0v-130 130Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/category.svg b/frontend/projects/admin/src/assets/svg/category.svg new file mode 100644 index 0000000000..b7996a6365 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/category.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m261-526 220-354 220 354H261ZM706-80q-74 0-124-50t-50-124q0-74 50-124t124-50q74 0 124 50t50 124q0 74-50 124T706-80Zm-586-25v-304h304v304H120Zm586.085-35Q754-140 787-173.085q33-33.084 33-81Q820-302 786.916-335q-33.085-33-81.001-33Q658-368 625-334.915q-33 33.084-33 81Q592-206 625.084-173q33.085 33 81.001 33ZM180-165h184v-184H180v184Zm189-421h224L481-767 369-586Zm112 0ZM364-349Zm342 95Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/check.svg b/frontend/projects/admin/src/assets/svg/check.svg new file mode 100644 index 0000000000..f3ab4225ee --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/check.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M18.9 35.7 7.7 24.5l2.15-2.15 9.05 9.05 19.2-19.2 2.15 2.15Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/close.svg b/frontend/projects/admin/src/assets/svg/close.svg new file mode 100644 index 0000000000..ded3e2a6e7 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m249-207-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/delete.svg b/frontend/projects/admin/src/assets/svg/delete.svg new file mode 100644 index 0000000000..af36de6ca5 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/delete.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M261 936q-24.75 0-42.375-17.625T201 876V306h-41v-60h188v-30h264v30h188v60h-41v570q0 24-18 42t-42 18H261Zm438-630H261v570h438V306ZM367 790h60V391h-60v399Zm166 0h60V391h-60v399ZM261 306v570-570Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/description.svg b/frontend/projects/admin/src/assets/svg/description.svg new file mode 100644 index 0000000000..fcdbdfcc32 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/description.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M319-250h322v-60H319v60Zm0-170h322v-60H319v60ZM220-80q-24 0-42-18t-18-42v-680q0-24 18-42t42-18h361l219 219v521q0 24-18 42t-42 18H220Zm331-554v-186H220v680h520v-494H551ZM220-820v186-186 680-680Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/details.svg b/frontend/projects/admin/src/assets/svg/details.svg new file mode 100644 index 0000000000..f5efea3c66 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/details.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m80 936 400-720 400 720H80Zm102-60h268V394L182 876Zm328 0h268L510 394v482Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/download.svg b/frontend/projects/admin/src/assets/svg/download.svg new file mode 100644 index 0000000000..f8c4fdc6c5 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/download.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M220 896q-24 0-42-18t-18-42V693h60v143h520V693h60v143q0 24-18 42t-42 18H220Zm260-153L287 550l43-43 120 120V256h60v371l120-120 43 43-193 193Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/edit.svg b/frontend/projects/admin/src/assets/svg/edit.svg new file mode 100644 index 0000000000..3a6ed16da0 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/edit.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M180 876h44l443-443-44-44-443 443v44Zm614-486L666 262l84-84 128 128-84 84ZM120 936V808l504-504 128 128-504 504H120Zm525-525-22-22 44 44-22-22Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/email-sent.svg b/frontend/projects/admin/src/assets/svg/email-sent.svg new file mode 100644 index 0000000000..f98549747c --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/email-sent.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-160v-640l760 320-760 320Zm60-93 544-227-544-230v168l242 62-242 60v167Zm0 0v-457 457Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/event.svg b/frontend/projects/admin/src/assets/svg/event.svg new file mode 100644 index 0000000000..4e91a2949f --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/event.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M7 40q-1.3 0-2.15-.85Q4 38.3 4 37v-7.65q1.85-.4 3.075-1.875T8.3 24q0-2-1.225-3.5T4 18.65V11q0-1.3.85-2.15Q5.7 8 7 8h34q1.3 0 2.15.85Q44 9.7 44 11v7.65q-1.85.35-3.075 1.85T39.7 24q0 2 1.225 3.475T44 29.35V37q0 1.3-.85 2.15Q42.3 40 41 40Zm0-3h34v-5.45q-1.9-1.3-3.1-3.25-1.2-1.95-1.2-4.3 0-2.35 1.2-4.3 1.2-1.95 3.1-3.25V11H7v5.45q1.95 1.3 3.125 3.25T11.3 24q0 2.35-1.175 4.3Q8.95 30.25 7 31.55Zm17-3.15q.6 0 1.05-.45.45-.45.45-1.05 0-.6-.45-1.05-.45-.45-1.05-.45-.6 0-1.05.45-.45.45-.45 1.05 0 .6.45 1.05.45.45 1.05.45Zm0-8.35q.6 0 1.05-.45.45-.45.45-1.05 0-.6-.45-1.05-.45-.45-1.05-.45-.6 0-1.05.45-.45.45-.45 1.05 0 .6.45 1.05.45.45 1.05.45Zm0-8.35q.6 0 1.05-.45.45-.45.45-1.05 0-.6-.45-1.05-.45-.45-1.05-.45-.6 0-1.05.45-.45.45-.45 1.05 0 .6.45 1.05.45.45 1.05.45ZM24 24Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/groups.svg b/frontend/projects/admin/src/assets/svg/groups.svg new file mode 100644 index 0000000000..9b3c14682d --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/groups.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M0 36v-2.65q0-1.95 2.1-3.15T7.5 29q.65 0 1.2.025.55.025 1.1.125-.4.85-.6 1.725-.2.875-.2 1.875V36Zm12 0v-3.25q0-3.25 3.325-5.25t8.675-2q5.4 0 8.7 2 3.3 2 3.3 5.25V36Zm27 0v-3.25q0-1-.175-1.875t-.575-1.725q.55-.1 1.1-.125Q39.9 29 40.5 29q3.4 0 5.45 1.2Q48 31.4 48 33.35V36Zm-15-7.5q-4 0-6.5 1.2T15 32.75V33h18v-.3q0-1.8-2.475-3T24 28.5Zm-16.5-1q-1.45 0-2.475-1.025Q4 25.45 4 24q0-1.45 1.025-2.475Q6.05 20.5 7.5 20.5q1.45 0 2.475 1.025Q11 22.55 11 24q0 1.45-1.025 2.475Q8.95 27.5 7.5 27.5Zm33 0q-1.45 0-2.475-1.025Q37 25.45 37 24q0-1.45 1.025-2.475Q39.05 20.5 40.5 20.5q1.45 0 2.475 1.025Q44 22.55 44 24q0 1.45-1.025 2.475Q41.95 27.5 40.5 27.5ZM24 24q-2.5 0-4.25-1.75T18 18q0-2.55 1.75-4.275Q21.5 12 24 12q2.55 0 4.275 1.725Q30 15.45 30 18q0 2.5-1.725 4.25T24 24Zm0-9q-1.25 0-2.125.85T21 18q0 1.25.875 2.125T24 21q1.3 0 2.15-.875Q27 19.25 27 18q0-1.3-.85-2.15Q25.3 15 24 15Zm0 18Zm0-15Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/home.svg b/frontend/projects/admin/src/assets/svg/home.svg new file mode 100644 index 0000000000..6a001268a4 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/home.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M11 39h7.5V26.5h11V39H37V19.5L24 9.75 11 19.5Zm-3 3V18L24 6l16 12v24H26.5V29.5h-5V42Zm16-17.65Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/hourglass-top.svg b/frontend/projects/admin/src/assets/svg/hourglass-top.svg new file mode 100644 index 0000000000..eb4fa09eb7 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/hourglass-top.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M316-140h328v-127q0-70-47.5-120.5T480-438q-69 0-116.5 50.5T316-267v127ZM160-80v-60h96v-127q0-70 36.5-128.5T394-480q-65-26-101.5-85T256-694v-126h-96v-60h640v60h-96v126q0 70-36.5 129T566-480q65 26 101.5 84.5T704-267v127h96v60H160Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/info.svg b/frontend/projects/admin/src/assets/svg/info.svg new file mode 100644 index 0000000000..cb0add7937 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/info.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M453-280h60v-240h-60v240Zm26.982-314q14.018 0 23.518-9.2T513-626q0-14.45-9.482-24.225-9.483-9.775-23.5-9.775-14.018 0-23.518 9.775T447-626q0 13.6 9.482 22.8 9.483 9.2 23.5 9.2Zm.284 514q-82.734 0-155.5-31.5t-127.266-86q-54.5-54.5-86-127.341Q80-397.681 80-480.5q0-82.819 31.5-155.659Q143-709 197.5-763t127.341-85.5Q397.681-880 480.5-880q82.819 0 155.659 31.5Q709-817 763-763t85.5 127Q880-563 880-480.266q0 82.734-31.5 155.5T763-197.684q-54 54.316-127 86Q563-80 480.266-80Zm.234-60Q622-140 721-239.5t99-241Q820-622 721.188-721 622.375-820 480-820q-141 0-240.5 98.812Q140-622.375 140-480q0 141 99.5 240.5t241 99.5Zm-.5-340Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/insights.svg b/frontend/projects/admin/src/assets/svg/insights.svg new file mode 100644 index 0000000000..2fc09b2df5 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/insights.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M109.912-150Q81-150 60.5-170.589 40-191.177 40-220.089 40-249 60.494-269.5t49.273-20.5q5.233 0 10.233.5 5 .5 13 2.5l200-200q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q371.177-580 400.089-580 429-580 449.5-559.366t20.5 49.61Q470-508 467-487l110 110q8-2 13-2.5t10-.5q5 0 10 .5t13 2.5l160-160q-2-8-2.5-13t-.5-10.233q0-28.779 20.589-49.273Q821.177-630 850.089-630 879-630 899.5-609.411q20.5 20.588 20.5 49.5Q920-531 899.506-510.5T850.233-490Q845-490 840-490.5q-5-.5-13-2.5L667-333q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q628.823-240 599.911-240 571-240 550.5-260.494T530-309.767q0-5.233.5-10.233.5-5 2.5-13L423-443q-8 2-13 2.5t-10.25.5q-1.75 0-22.75-3L177-243q2 8 2.5 13t.5 10.233q0 28.779-20.589 49.273Q138.823-150 109.912-150ZM160-592l-20.253-43.747L96-656l43.747-20.253L160-720l20.253 43.747L224-656l-43.747 20.253L160-592Zm440-51-30.717-66.283L503-740l66.283-30.717L600-837l30.717 66.283L697-740l-66.283 30.717L600-643Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/key.svg b/frontend/projects/admin/src/assets/svg/key.svg new file mode 100644 index 0000000000..2098912039 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/key.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M280 644q-28 0-48-20t-20-48q0-28 20-48t48-20q28 0 48 20t20 48q0 28-20 48t-48 20Zm0 172q-100 0-170-70T40 576q0-100 70-170t170-70q72 0 126 34t85 103h356l113 113-167 153-88-64-88 64-75-60h-51q-25 60-78.5 98.5T280 816Zm0-60q58 0 107-38.5t63-98.5h114l54 45 88-63 82 62 85-79-51-51H450q-12-56-60-96.5T280 396q-75 0-127.5 52.5T100 576q0 75 52.5 127.5T280 756Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/lock-open.svg b/frontend/projects/admin/src/assets/svg/lock-open.svg new file mode 100644 index 0000000000..5316085624 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/lock-open.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-634h390v-96q0-54.167-37.882-92.083-37.883-37.917-92-37.917Q426-860 388-822.083 350-784.167 350-730h-60q0-79 55.606-134.5t134.5-55.5Q559-920 614.5-864.425T670-730v96h70q24.75 0 42.375 17.625T800-574v434q0 24.75-17.625 42.375T740-80H220q-24.75 0-42.375-17.625T160-140v-434q0-24.75 17.625-42.375T220-634Zm0 494h520v-434H220v434Zm260.168-140Q512-280 534.5-302.031T557-355q0-30-22.668-54.5t-54.5-24.5Q448-434 425.5-409.5t-22.5 55q0 30.5 22.668 52.5t54.5 22ZM220-140v-434 434Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/lock.svg b/frontend/projects/admin/src/assets/svg/lock.svg new file mode 100644 index 0000000000..9ef76e5368 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/lock.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M220-80q-24.75 0-42.375-17.625T160-140v-434q0-24.75 17.625-42.375T220-634h70v-96q0-78.85 55.606-134.425Q401.212-920 480.106-920T614.5-864.425Q670-808.85 670-730v96h70q24.75 0 42.375 17.625T800-574v434q0 24.75-17.625 42.375T740-80H220Zm0-60h520v-434H220v434Zm260.168-140Q512-280 534.5-302.031T557-355q0-30-22.668-54.5t-54.5-24.5Q448-434 425.5-409.5t-22.5 55q0 30.5 22.668 52.5t54.5 22ZM350-634h260v-96q0-54.167-37.882-92.083-37.883-37.917-92-37.917Q426-860 388-822.083 350-784.167 350-730v96ZM220-140v-434 434Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/organization.svg b/frontend/projects/admin/src/assets/svg/organization.svg new file mode 100644 index 0000000000..e970c3e370 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/organization.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M4 42V6h19.5v8.25H44V42Zm3-3h5.25v-5.25H7Zm0-8.25h5.25V25.5H7Zm0-8.25h5.25v-5.25H7Zm0-8.25h5.25V9H7ZM15.25 39h5.25v-5.25h-5.25Zm0-8.25h5.25V25.5h-5.25Zm0-8.25h5.25v-5.25h-5.25Zm0-8.25h5.25V9h-5.25ZM23.5 39H41V17.25H23.5v5.25h4v3h-4v5.25h4v3h-4Zm9.25-13.5v-3h3v3Zm0 8.25v-3h3v3Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/outgoing-mail.svg b/frontend/projects/admin/src/assets/svg/outgoing-mail.svg new file mode 100644 index 0000000000..1fc98d35a9 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/outgoing-mail.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m734-164-42-42 73-74H584v-60h181l-73-74 42-42 146 146-146 146ZM140-280q-24.75 0-42.375-17.625T80-340v-440q0-24.75 17.625-42.375T140-840h560q24.75 0 42.375 17.625T760-780v232q-7.5-1-15-1.5t-15-.5q-8 0-15 .5t-15 1.5v-185L416-532 140-732v392h351q-1 8-1 13.5v13.75q0 8.25.5 16.5T493-280H140Zm36-500 240 174 246-174H176Zm-36 440v-440 440Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/payments.svg b/frontend/projects/admin/src/assets/svg/payments.svg new file mode 100644 index 0000000000..49cb64615a --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/payments.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M540 636q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35ZM220 776q-24.75 0-42.375-17.625T160 716V316q0-24.75 17.625-42.375T220 256h640q24.75 0 42.375 17.625T920 316v400q0 24.75-17.625 42.375T860 776H220Zm100-60h440q0-42 29-71t71-29V416q-42 0-71-29t-29-71H320q0 42-29 71t-71 29v200q42 0 71 29t29 71Zm480 180H100q-24.75 0-42.375-17.625T40 836V376h60v460h700v60ZM220 716V316v400Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/percent.svg b/frontend/projects/admin/src/assets/svg/percent.svg new file mode 100644 index 0000000000..e15c8b523c --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/percent.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M289.899-540Q236-540 198-578.101t-38-92Q160-724 198.101-762t92-38Q344-800 382-761.899t38 92Q420-616 381.899-578t-92 38Zm-.017-60Q319-600 339.5-620.382q20.5-20.383 20.5-49.5Q360-699 339.618-719.5q-20.383-20.5-49.5-20.5Q261-740 240.5-719.618q-20.5 20.383-20.5 49.5Q220-641 240.382-620.5q20.383 20.5 49.5 20.5Zm380.017 440Q616-160 578-198.101t-38-92Q540-344 578.101-382t92-38Q724-420 762-381.899t38 92Q800-236 761.899-198t-92 38Zm-.017-60Q699-220 719.5-240.382q20.5-20.383 20.5-49.5Q740-319 719.618-339.5q-20.383-20.5-49.5-20.5Q641-360 620.5-339.618q-20.5 20.383-20.5 49.5Q600-261 620.382-240.5q20.383 20.5 49.5 20.5ZM202-160l-42-42 598-598 42 42-598 598Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/piggy-bank.svg b/frontend/projects/admin/src/assets/svg/piggy-bank.svg new file mode 100644 index 0000000000..db27be1d2a --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/piggy-bank.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M640-520q17 0 28.5-11.5T680-560q0-17-11.5-28.5T640-600q-17 0-28.5 11.5T600-560q0 17 11.5 28.5T640-520ZM320-620h200v-60H320v60ZM180-120q-34-114-67-227.5T80-580q0-92 64-156t156-64h200q29-38 70.5-59t89.5-21q25 0 42.5 17.5T720-820q0 6-1.5 12t-3.5 11q-4 11-7.5 22.5T702-751l91 91h87v279l-113 37-67 224H480v-80h-80v80H180Zm45-60h115v-80h200v80h115l63-210 102-35v-175h-52L640-728q1-25 6.5-48.5T658-824q-38 10-72 29.5T534-740H300q-66.286 0-113.143 46.857T140-580q0 103.158 29 201.579T225-180Zm255-322Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/qr-code.svg b/frontend/projects/admin/src/assets/svg/qr-code.svg new file mode 100644 index 0000000000..6ab295d9df --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/qr-code.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M520-120v-80h80v80h-80Zm-80-80v-200h80v200h-80Zm320-120v-160h80v160h-80Zm-80-160v-80h80v80h-80Zm-480 80v-80h80v80h-80Zm-80-80v-80h80v80h-80Zm360-280v-80h80v80h-80ZM170-650h140v-140H170v140Zm-50 50v-240h240v240H120Zm50 430h140v-140H170v140Zm-50 50v-240h240v240H120Zm530-530h140v-140H650v140Zm-50 50v-240h240v240H600Zm80 480v-120h-80v-80h160v120h80v80H680ZM520-400v-80h160v80H520Zm-160 0v-80h-80v-80h240v80h-80v80h-80Zm40-200v-160h80v80h80v80H400Zm-190-90v-60h60v60h-60Zm0 480v-60h60v60h-60Zm480-480v-60h60v60h-60Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/reset.svg b/frontend/projects/admin/src/assets/svg/reset.svg new file mode 100644 index 0000000000..d804df854d --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/reset.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M132 896v-60h123l-14-14q-60-60-86.5-121.5T128 580q0-109 62.5-195T355 266v62q-76 30-121.5 99T188 580q0 52 17.5 98.5T265 763l30 27V673h60v223H132Zm699-334h-60q-2-51-20-94.5T695 389l-30-27v117h-60V256h223v60H704l15 14q58 55 84 115t28 117Zm-135 454-5-48q-20-6-41-17.5T616 925l-42 20-35-54 38-30q-5-23-5-41.5t5-41.5l-38-30 35-55 42 20q13-12 34-24t41-18l5-49h60l6 49q20 6 41 18t34 24l42-20 35 55-38 30q5 23 5 41.5t-5 41.5l38 30-35 54-42-20q-13 14-34 25.5T762 968l-6 48h-60Zm30-95q44 0 73-29t29-73q0-44-29-73t-73-29q-44 0-73 29t-29 73q0 44 29 73t73 29Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/search.svg b/frontend/projects/admin/src/assets/svg/search.svg new file mode 100644 index 0000000000..f51c92487b --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/search.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M796-121 533-384q-30 26-69.959 40.5T378-329q-108.162 0-183.081-75Q120-479 120-585t75-181q75-75 181.5-75t181 75Q632-691 632-584.85 632-542 618-502q-14 40-42 75l264 262-44 44ZM377-389q81.25 0 138.125-57.5T572-585q0-81-56.875-138.5T377-781q-82.083 0-139.542 57.5Q180-666 180-585t57.458 138.5Q294.917-389 377-389Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/settings.svg b/frontend/projects/admin/src/assets/svg/settings.svg new file mode 100644 index 0000000000..0d6f5507c4 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/settings.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m19.4 44-1-6.3q-.95-.35-2-.95t-1.85-1.25l-5.9 2.7L4 30l5.4-3.95q-.1-.45-.125-1.025Q9.25 24.45 9.25 24q0-.45.025-1.025T9.4 21.95L4 18l4.65-8.2 5.9 2.7q.8-.65 1.85-1.25t2-.9l1-6.35h9.2l1 6.3q.95.35 2.025.925Q32.7 11.8 33.45 12.5l5.9-2.7L44 18l-5.4 3.85q.1.5.125 1.075.025.575.025 1.075t-.025 1.05q-.025.55-.125 1.05L44 30l-4.65 8.2-5.9-2.7q-.8.65-1.825 1.275-1.025.625-2.025.925l-1 6.3ZM24 30.5q2.7 0 4.6-1.9 1.9-1.9 1.9-4.6 0-2.7-1.9-4.6-1.9-1.9-4.6-1.9-2.7 0-4.6 1.9-1.9 1.9-1.9 4.6 0 2.7 1.9 4.6 1.9 1.9 4.6 1.9Zm0-3q-1.45 0-2.475-1.025Q20.5 25.45 20.5 24q0-1.45 1.025-2.475Q22.55 20.5 24 20.5q1.45 0 2.475 1.025Q27.5 22.55 27.5 24q0 1.45-1.025 2.475Q25.45 27.5 24 27.5Zm0-3.5Zm-2.2 17h4.4l.7-5.6q1.65-.4 3.125-1.25T32.7 32.1l5.3 2.3 2-3.6-4.7-3.45q.2-.85.325-1.675.125-.825.125-1.675 0-.85-.1-1.675-.1-.825-.35-1.675L40 17.2l-2-3.6-5.3 2.3q-1.15-1.3-2.6-2.175-1.45-.875-3.2-1.125L26.2 7h-4.4l-.7 5.6q-1.7.35-3.175 1.2-1.475.85-2.625 2.1L10 13.6l-2 3.6 4.7 3.45q-.2.85-.325 1.675-.125.825-.125 1.675 0 .85.125 1.675.125.825.325 1.675L8 30.8l2 3.6 5.3-2.3q1.2 1.2 2.675 2.05Q19.45 35 21.1 35.4Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/subscription.svg b/frontend/projects/admin/src/assets/svg/subscription.svg new file mode 100644 index 0000000000..89fa2db197 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/subscription.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M7 25.85V31h34v-5.15ZM7 4h34q1.25 0 2.125.875T44 7v24q0 1.25-.875 2.125T41 34h-9.7v10L24 40.3 16.7 44V34H7q-1.25 0-2.125-.875T4 31V7q0-1.25.875-2.125T7 4Zm0 16.45h34V7H7ZM7 31V7v24Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/ticket-reserved.svg b/frontend/projects/admin/src/assets/svg/ticket-reserved.svg new file mode 100644 index 0000000000..c34fbb89e3 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/ticket-reserved.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m376-318 104-81 104 81-40-128 98-82H522l-42-122-42 122H318l98 82-40 128ZM140-160q-25 0-42.5-17.5T80-220v-132q0-10 5.5-17.5T100-380q31-11 48.5-39.5T166-480q0-31-17.5-60T100-580q-9-3-14.5-10.5T80-608v-132q0-25 17.5-42.5T140-800h680q25 0 42.5 17.5T880-740v132q0 10-5.5 17.5T860-580q-31 11-48.5 40T794-480q0 32 17.5 60.5T860-380q9 3 14.5 10.5T880-352v132q0 25-17.5 42.5T820-160H140Zm0-60h680v-109q-38-26-62-65t-24-86q0-47 24-86t62-65v-109H140v109q39 26 62.5 65t23.5 86q0 47-23.5 86T140-329v109Zm340-260Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/ticket.svg b/frontend/projects/admin/src/assets/svg/ticket.svg new file mode 100644 index 0000000000..01b18f9860 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/ticket.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M480 773q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm0-167q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm0-167q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm340 457H140q-24.75 0-42.375-17.625T80 836V683q37-8 61.5-37.5T166 576q0-40-24.5-70T80 469V316q0-24.75 17.625-42.375T140 256h680q24.75 0 42.375 17.625T880 316v153q-37 7-61.5 37T794 576q0 40 24.5 69.5T880 683v153q0 24.75-17.625 42.375T820 896Zm0-60V727q-38-26-62-65t-24-86q0-47 24-86t62-65V316H140v109q39 26 62.5 65t23.5 86q0 47-23.5 86T140 727v109h680ZM480 576Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/visibility-on.svg b/frontend/projects/admin/src/assets/svg/visibility-on.svg new file mode 100644 index 0000000000..659d390075 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/visibility-on.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M480.118 726Q551 726 600.5 676.382q49.5-49.617 49.5-120.5Q650 485 600.382 435.5q-49.617-49.5-120.5-49.5Q409 386 359.5 435.618q-49.5 49.617-49.5 120.5Q310 627 359.618 676.5q49.617 49.5 120.5 49.5Zm-.353-58Q433 668 400.5 635.265q-32.5-32.736-32.5-79.5Q368 509 400.735 476.5q32.736-32.5 79.5-32.5Q527 444 559.5 476.735q32.5 32.736 32.5 79.5Q592 603 559.265 635.5q-32.736 32.5-79.5 32.5ZM480 856q-146 0-264-83T40 556q58-134 176-217t264-83q146 0 264 83t176 217q-58 134-176 217t-264 83Zm0-300Zm-.169 240Q601 796 702.5 730.5 804 665 857 556q-53-109-154.331-174.5-101.332-65.5-222.5-65.5Q359 316 257.5 381.5 156 447 102 556q54 109 155.331 174.5 101.332 65.5 222.5 65.5Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/visibilityoff.svg b/frontend/projects/admin/src/assets/svg/visibilityoff.svg new file mode 100644 index 0000000000..90e94063c4 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/visibilityoff.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="m629 637-44-44q26-71-27-118t-115-24l-44-44q17-11 38-16t43-5q71 0 120.5 49.5T650 556q0 22-5.5 43.5T629 637Zm129 129-40-40q49-36 85.5-80.5T857 556q-50-111-150-175.5T490 316q-42 0-86 8t-69 19l-46-47q35-16 89.5-28T485 256q143 0 261.5 81.5T920 556q-26 64-67 117t-95 93Zm58 226L648 827q-35 14-79 21.5t-89 7.5q-146 0-265-81.5T40 556q20-52 55.5-101.5T182 360L56 234l42-43 757 757-39 44ZM223 402q-37 27-71.5 71T102 556q51 111 153.5 175.5T488 796q33 0 65-4t48-12l-64-64q-11 5-27 7.5t-30 2.5q-70 0-120-49t-50-121q0-15 2.5-30t7.5-27l-97-97Zm305 142Zm-116 58Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/waiting-list.svg b/frontend/projects/admin/src/assets/svg/waiting-list.svg new file mode 100644 index 0000000000..3376369847 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/waiting-list.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M120-160v-60h480v60H120Zm519.894-290Q561-450 505.5-505.606t-55.5-134.5Q450-719 505.606-774.5t134.5-55.5Q719-830 774.5-774.394t55.5 134.5Q830-561 774.394-505.5t-134.5 55.5ZM120-500v-60h262q5.32 16.323 12.16 31.161Q401-514 409-500H120Zm0 170v-60h419q13.8 6.364 29.4 10.682Q584-375 600-373v43H120Zm500-190h40v-160h-40v160Zm20-200q8 0 14-6t6-14q0-8-6-14t-14-6q-8 0-14 6t-6 14q0 8 6 14t14 6Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/assets/svg/warning.svg b/frontend/projects/admin/src/assets/svg/warning.svg new file mode 100644 index 0000000000..21f59d38a4 --- /dev/null +++ b/frontend/projects/admin/src/assets/svg/warning.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="m40-120 440-760 440 760H40Zm104-60h672L480-760 144-180Zm340.175-57q12.825 0 21.325-8.675 8.5-8.676 8.5-21.5 0-12.825-8.675-21.325-8.676-8.5-21.5-8.5-12.825 0-21.325 8.675-8.5 8.676-8.5 21.5 0 12.825 8.675 21.325 8.676 8.5 21.5 8.5ZM454-348h60v-224h-60v224Zm26-122Z"/></svg> \ No newline at end of file diff --git a/frontend/projects/admin/src/environments/environment.prod.ts b/frontend/projects/admin/src/environments/environment.prod.ts new file mode 100644 index 0000000000..3612073bc3 --- /dev/null +++ b/frontend/projects/admin/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/frontend/projects/admin/src/environments/environment.ts b/frontend/projects/admin/src/environments/environment.ts new file mode 100644 index 0000000000..f56ff47022 --- /dev/null +++ b/frontend/projects/admin/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/frontend/projects/admin/src/favicon.ico b/frontend/projects/admin/src/favicon.ico new file mode 100644 index 0000000000..997406ad22 Binary files /dev/null and b/frontend/projects/admin/src/favicon.ico differ diff --git a/frontend/projects/admin/src/index.html b/frontend/projects/admin/src/index.html new file mode 100644 index 0000000000..42dae1e082 --- /dev/null +++ b/frontend/projects/admin/src/index.html @@ -0,0 +1,13 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Alf.io</title> + <base href="/"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="icon" type="image/x-icon" href="favicon.ico"> +</head> +<body> + <app-root></app-root> +</body> +</html> diff --git a/frontend/projects/admin/src/main.ts b/frontend/projects/admin/src/main.ts new file mode 100644 index 0000000000..484b5f3d3a --- /dev/null +++ b/frontend/projects/admin/src/main.ts @@ -0,0 +1,12 @@ +import {enableProdMode} from '@angular/core'; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; + +import {AppModule} from './app/app.module'; +import {environment} from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/frontend/projects/admin/src/polyfills.ts b/frontend/projects/admin/src/polyfills.ts new file mode 100644 index 0000000000..24785a35a2 --- /dev/null +++ b/frontend/projects/admin/src/polyfills.ts @@ -0,0 +1,54 @@ +/*************************************************************************************************** + * 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 recent versions of Safari, Chrome (including + * Opera), Edge on the desktop, and iOS 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'; + * + * 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/projects/admin/src/styles.scss b/frontend/projects/admin/src/styles.scss new file mode 100644 index 0000000000..3394866fa0 --- /dev/null +++ b/frontend/projects/admin/src/styles.scss @@ -0,0 +1,13 @@ +/* You can add global styles to this file, and also import other style files */ + +@import "font/font.scss"; + +/* Importing Bootstrap SCSS file. */ +@import "bootstrap/scss/bootstrap"; + +.btn, +.input-group { + svg-icon { + line-height: 0; + } +} diff --git a/frontend/projects/admin/src/test.ts b/frontend/projects/admin/src/test.ts new file mode 100644 index 0000000000..b5adc13140 --- /dev/null +++ b/frontend/projects/admin/src/test.ts @@ -0,0 +1,23 @@ +// 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'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + <T>(id: string): T; + keys(): string[]; + }; +}; + +// 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().forEach(context); diff --git a/frontend/projects/admin/tsconfig.app.json b/frontend/projects/admin/tsconfig.app.json new file mode 100644 index 0000000000..fd37f74d77 --- /dev/null +++ b/frontend/projects/admin/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/frontend/projects/admin/tsconfig.spec.json b/frontend/projects/admin/tsconfig.spec.json new file mode 100644 index 0000000000..a9c0752ffe --- /dev/null +++ b/frontend/projects/admin/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/frontend/projects/common/README.md b/frontend/projects/common/README.md new file mode 100644 index 0000000000..d932e1b60f --- /dev/null +++ b/frontend/projects/common/README.md @@ -0,0 +1,24 @@ +# Common + +This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.0. + +## Code scaffolding + +Run `ng generate component component-name --project common` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project common`. +> Note: Don't forget to add `--project common` or else it will be added to the default project in your `angular.json` file. + +## Build + +Run `ng build common` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Publishing + +After building your library with `ng build common`, go to the dist folder `cd dist/common` and run `npm publish`. + +## Running unit tests + +Run `ng test common` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/frontend/projects/common/ng-package.json b/frontend/projects/common/ng-package.json new file mode 100644 index 0000000000..1cb4d65793 --- /dev/null +++ b/frontend/projects/common/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist/common", + "lib": { + "entryFile": "src/public-api.ts" + } +} \ No newline at end of file diff --git a/frontend/projects/common/package-lock.json b/frontend/projects/common/package-lock.json new file mode 100644 index 0000000000..99d920d310 --- /dev/null +++ b/frontend/projects/common/package-lock.json @@ -0,0 +1,117 @@ +{ + "name": "common", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "common", + "version": "0.0.1", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^14.1.1", + "@angular/core": "^14.1.1" + } + }, + "node_modules/@angular/common": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.2.12.tgz", + "integrity": "sha512-oZunh9wfInFWhNO1P8uoEs/o4u8kerKMhw8GruywKm1TV7gHDP2Fi5WHGjFqq3XYptgBTPCTSEfyLX6Cwq1PUw==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.2.12", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/core": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.2.12.tgz", + "integrity": "sha512-sGQxU5u4uawwvJa6jOTmGoisJiQ5HIN/RoBw99CmoqZIVyUSg9IRJJC1KVdH8gbpWBNLkElZv21lwJTL/msWyg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.11.4 || ~0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "node_modules/zone.js": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.12.0.tgz", + "integrity": "sha512-XtC+I5dXU14HrzidAKBNMqneIVUykLEAA1x+v4KVrd6AUPWlwYORF8KgsVqvgdHiKZ4BkxxjvYi/ksEixTPR0Q==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + } + } + }, + "dependencies": { + "@angular/common": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.2.12.tgz", + "integrity": "sha512-oZunh9wfInFWhNO1P8uoEs/o4u8kerKMhw8GruywKm1TV7gHDP2Fi5WHGjFqq3XYptgBTPCTSEfyLX6Cwq1PUw==", + "peer": true, + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/core": { + "version": "14.2.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.2.12.tgz", + "integrity": "sha512-sGQxU5u4uawwvJa6jOTmGoisJiQ5HIN/RoBw99CmoqZIVyUSg9IRJJC1KVdH8gbpWBNLkElZv21lwJTL/msWyg==", + "peer": true, + "requires": { + "tslib": "^2.3.0" + } + }, + "rxjs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "zone.js": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.12.0.tgz", + "integrity": "sha512-XtC+I5dXU14HrzidAKBNMqneIVUykLEAA1x+v4KVrd6AUPWlwYORF8KgsVqvgdHiKZ4BkxxjvYi/ksEixTPR0Q==", + "peer": true, + "requires": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/frontend/projects/common/package.json b/frontend/projects/common/package.json new file mode 100644 index 0000000000..1a882b8181 --- /dev/null +++ b/frontend/projects/common/package.json @@ -0,0 +1,11 @@ +{ + "name": "common", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "^14.1.1", + "@angular/core": "^14.1.1" + }, + "dependencies": { + "tslib": "^2.3.0" + } +} diff --git a/frontend/projects/common/src/lib/alfio-common.module.ts b/frontend/projects/common/src/lib/alfio-common.module.ts new file mode 100644 index 0000000000..7888f4b28b --- /dev/null +++ b/frontend/projects/common/src/lib/alfio-common.module.ts @@ -0,0 +1,13 @@ +import {NgModule} from '@angular/core'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + ], + exports: [ + ] +}) +export class AlfioCommonModule { } diff --git a/frontend/projects/common/src/lib/xsrf.ts b/frontend/projects/common/src/lib/xsrf.ts new file mode 100644 index 0000000000..957fb52fa5 --- /dev/null +++ b/frontend/projects/common/src/lib/xsrf.ts @@ -0,0 +1,110 @@ +import {Inject, Injectable, Injector, INJECTOR, PLATFORM_ID} from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, + HttpXsrfTokenExtractor +} from '@angular/common/http'; +import {DOCUMENT} from '@angular/common'; +import {Observable} from 'rxjs'; +import {tap} from 'rxjs/operators'; + +const GID_HEADER_NAME = 'x-auth-token'; +const XSRF_TOKEN_HEADER = 'xsrf-token'; + +@Injectable() +export class DOMXsrfTokenExtractor implements HttpXsrfTokenExtractor { + + lastToken: string | null = null; + + constructor(@Inject(DOCUMENT) private doc: Document, + @Inject(PLATFORM_ID) private platform: string) {} + + getToken(): string | null { + if (this.platform === 'server') { + return null; + } + return this.doc.head.querySelector<HTMLMetaElement>('meta[name="XSRF_TOKEN"]')?.content || null; + } + + updateToken(newVal: string | null): void { + if (newVal == null || newVal === this.lastToken) { + return; + } + const element = retrieveHeadElement('XSRF_TOKEN', this.doc); + element.content = newVal; + this.lastToken = newVal; + } +} + +@Injectable() +export class DOMGidExtractor { + + lastToken: string | null = null; + + constructor(@Inject(DOCUMENT) private doc: Document, + @Inject(PLATFORM_ID) private platform: string) {} + + getToken(): string | null { + if (this.platform === 'server') { + return null; + } + return this.doc.head.querySelector<HTMLMetaElement>('meta[name="GID"]')?.content || null; + } + + updateToken(newVal: string | null): void { + if (newVal == null || newVal === this.lastToken) { + return; + } + const element = retrieveHeadElement('GID', this.doc); + element.content = newVal; + this.lastToken = newVal; + } +} + +function retrieveHeadElement(name: string, doc: Document): HTMLMetaElement { + let element = doc.head.querySelector<HTMLMetaElement>(`meta[name="${name}"]`); + if (element == null) { + // it should only happen in dev mode + element = doc.createElement('meta'); + element.name = name; + doc.head.append(element); + } + return element; +} + +@Injectable() +export class AuthTokenInterceptor implements HttpInterceptor { + + private domGidExtractor: DOMGidExtractor | null = null; + private domXsrfTokenExtractor: DOMXsrfTokenExtractor | null = null; + + constructor(@Inject(INJECTOR) private injector: Injector) { + } + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + this.init(); + return next.handle(req.clone({ + headers: req.headers.set(GID_HEADER_NAME, this.domGidExtractor?.getToken() || '') + })).pipe(tap(response => { + if (response instanceof HttpResponse) { + if (response.headers.has(GID_HEADER_NAME)) { + this.domGidExtractor?.updateToken(response.headers.get(GID_HEADER_NAME)); + } + if (req.method === 'GET' && response.headers.has(XSRF_TOKEN_HEADER)) { + this.domXsrfTokenExtractor?.updateToken(response.headers.get(XSRF_TOKEN_HEADER)); + } + } + })); + } + + private init(): void { + if (this.domGidExtractor == null) { + this.domGidExtractor = this.injector.get(DOMGidExtractor); + this.domXsrfTokenExtractor = this.injector.get(DOMXsrfTokenExtractor); + } + } + +} diff --git a/frontend/projects/common/src/public-api.ts b/frontend/projects/common/src/public-api.ts new file mode 100644 index 0000000000..d6b0386644 --- /dev/null +++ b/frontend/projects/common/src/public-api.ts @@ -0,0 +1,6 @@ +/* + * Public API Surface of common + */ + +export * from './lib/alfio-common.module'; +export * from './lib/xsrf'; diff --git a/frontend/projects/common/style/font/font.scss b/frontend/projects/common/style/font/font.scss new file mode 100644 index 0000000000..84430c82e1 --- /dev/null +++ b/frontend/projects/common/style/font/font.scss @@ -0,0 +1,65 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.cyrillic-ext.woff2') format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.cyrillic.woff2') format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.greek-ext.woff2') format('woff2'); + unicode-range: U+1F00-1FFF; +} +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.greek.woff2') format('woff2'); + unicode-range: U+0370-03FF; +} +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.vietnamese.woff2') format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.latin-ext.woff2') format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url('/resources/font/SourceSansPro-Regular.latin.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +$font-family-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; diff --git a/frontend/projects/common/tsconfig.lib.json b/frontend/projects/common/tsconfig.lib.json new file mode 100644 index 0000000000..543fd474ab --- /dev/null +++ b/frontend/projects/common/tsconfig.lib.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/lib", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "**/*.spec.ts" + ] +} diff --git a/frontend/projects/common/tsconfig.lib.prod.json b/frontend/projects/common/tsconfig.lib.prod.json new file mode 100644 index 0000000000..06de549e10 --- /dev/null +++ b/frontend/projects/common/tsconfig.lib.prod.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/frontend/projects/common/tsconfig.spec.json b/frontend/projects/common/tsconfig.spec.json new file mode 100644 index 0000000000..ce7048bc2c --- /dev/null +++ b/frontend/projects/common/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/frontend/projects/public/README.md b/frontend/projects/public/README.md new file mode 100644 index 0000000000..9ae9e5ecc2 --- /dev/null +++ b/frontend/projects/public/README.md @@ -0,0 +1,29 @@ +[![Build Status](https://travis-ci.org/alfio-event/alf.io-public-frontend.svg?branch=master)](https://travis-ci.org/alfio-event/alf.io-public-frontend) + +# AlfioPublicFrontend + +This is the "public" frontend of [alf.io](https://alf.io/). + +## Development server + +Run `npm run start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/frontend/projects/public/e2e/protractor.conf.js b/frontend/projects/public/e2e/protractor.conf.js new file mode 100644 index 0000000000..86776a391a --- /dev/null +++ b/frontend/projects/public/e2e/protractor.conf.js @@ -0,0 +1,28 @@ +// 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: [ + './src/**/*.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: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/frontend/projects/public/e2e/src/app.e2e-spec.ts b/frontend/projects/public/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..89892fa716 --- /dev/null +++ b/frontend/projects/public/e2e/src/app.e2e-spec.ts @@ -0,0 +1,23 @@ +import {AppPage} from './app.po'; +import {browser, logging} from 'protractor'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('Welcome to alfio-public-frontend!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + })); + }); +}); diff --git a/frontend/projects/public/e2e/src/app.po.ts b/frontend/projects/public/e2e/src/app.po.ts new file mode 100644 index 0000000000..371ad6347d --- /dev/null +++ b/frontend/projects/public/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import {browser, by, element} from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getTitleText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/frontend/projects/public/e2e/tsconfig.e2e.json b/frontend/projects/public/e2e/tsconfig.e2e.json new file mode 100644 index 0000000000..a6dd622028 --- /dev/null +++ b/frontend/projects/public/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/frontend/projects/public/package-lock.json b/frontend/projects/public/package-lock.json new file mode 100644 index 0000000000..1400ffec07 --- /dev/null +++ b/frontend/projects/public/package-lock.json @@ -0,0 +1,23443 @@ +{ + "name": "alfio-public-frontend", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "alfio-public-frontend", + "version": "0.0.0", + "dependencies": { + "@angular/animations": "^14.1.1", + "@angular/common": "^14.1.1", + "@angular/compiler": "^14.1.1", + "@angular/core": "^14.1.1", + "@angular/forms": "^14.1.1", + "@angular/localize": "^14.1.1", + "@angular/platform-browser": "^14.1.1", + "@angular/platform-browser-dynamic": "^14.1.1", + "@angular/router": "^14.1.1", + "@fortawesome/angular-fontawesome": "^0.11.1", + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-brands-svg-icons": "^5.15.3", + "@fortawesome/free-regular-svg-icons": "^5.15.3", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@ng-bootstrap/ng-bootstrap": "^13.0.0", + "@ng-select/ng-select": "^9.0.2", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "bootstrap": "^5.2.0", + "common": "^0.2.5", + "rxjs": "^6.6.3", + "tslib": "^2.0.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.1.1", + "@angular/cli": "^14.1.1", + "@angular/compiler-cli": "^14.1.1", + "@angular/language-service": "^14.1.1", + "@types/jasmine": "~2.8.8", + "@types/jasminewd2": "~2.0.3", + "@types/node": "^12.11.1", + "jasmine-core": "~3.8.0", + "jasmine-spec-reporter": "~5.0.0", + "karma": "~6.3.9", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~3.0.2", + "karma-jasmine": "~3.3.0", + "karma-jasmine-html-reporter": "^1.7.0", + "protractor": "~7.0.0", + "ts-node": "~7.0.0", + "tslint": "~6.1.0", + "typescript": "~4.7.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1401.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1401.1.tgz", + "integrity": "sha512-HIFrIwbjfXCOjbGlMpHzG3oQG0nM1opaFSeKi+JjzTIb0jWq2s8sJfn4tGOZEJU8aKtDpNYMcW+N3F0grZdR8w==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.1.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.1.1.tgz", + "integrity": "sha512-SlkWvTP6lrXOr5dOLtI56hSX7WuDznaUFHGfXpuwA+buiUgvigzO68VhKfsKn5I44J0PRpqXKPM7bxOXAOMe8Q==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1401.1", + "@angular-devkit/build-webpack": "0.1401.1", + "@angular-devkit/core": "14.1.1", + "@babel/core": "7.18.6", + "@babel/generator": "7.18.7", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.18.6", + "@babel/plugin-transform-async-to-generator": "7.18.6", + "@babel/plugin-transform-runtime": "7.18.6", + "@babel/preset-env": "7.18.6", + "@babel/runtime": "7.18.6", + "@babel/template": "7.18.6", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "14.1.1", + "ansi-colors": "4.1.3", + "babel-loader": "8.2.5", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.9.1", + "cacache": "16.1.1", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.1", + "esbuild-wasm": "0.14.49", + "glob": "8.0.3", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.0.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.0", + "mini-css-extract-plugin": "2.6.1", + "minimatch": "5.1.0", + "open": "8.4.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "6.0.1", + "piscina": "3.2.0", + "postcss": "8.4.14", + "postcss-import": "14.1.0", + "postcss-loader": "7.0.1", + "postcss-preset-env": "7.7.2", + "regenerator-runtime": "0.13.9", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.53.0", + "sass-loader": "13.0.2", + "semver": "7.3.7", + "source-map-loader": "4.0.0", + "source-map-support": "0.5.21", + "stylus": "0.58.1", + "stylus-loader": "7.0.0", + "terser": "5.14.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.4.0", + "webpack": "5.73.0", + "webpack-dev-middleware": "5.3.3", + "webpack-dev-server": "4.9.3", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.14.49" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0", + "@angular/localize": "^14.0.0", + "@angular/service-worker": "^14.0.0", + "karma": "^6.3.0", + "ng-packagr": "^14.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=4.6.2 <4.8" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", + "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helpers": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular-devkit/build-angular/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1401.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1401.1.tgz", + "integrity": "sha512-MY3KHLSRC6Ev4I9RLoAObyEoT95SYZSdnZQA+2WWcRXzrtCo48IfA1joojo6SLBd4k5uUisBs9aDK1NU8ugbQQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1401.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.1.1.tgz", + "integrity": "sha512-i5SiU/9xqKbhi5A2kq7ME5KWNXtVIlSLZ/HslGjsolZ4CO0LiZanywcE/HGL7681RVaWVeeSndmKQtqY3mPuNQ==", + "dev": true, + "dependencies": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.1.1.tgz", + "integrity": "sha512-9ymklxBm6ZxB4dvfsowyHQRx+DE7lQShDDMnwT2mPtH7SwbaLEUz02aL4W5BsuR6U1W+M181pZ4Igb3oq0AEoA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.1.1", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/animations": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-14.1.1.tgz", + "integrity": "sha512-/fXzJzr8Pr7/xpwErX9PjbIc790RF818WgW7SUNev6pN6UUq3gvjmPjDTdZBZfiAZWY49nBLibPo2FvmyCP7tg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.1.1" + } + }, + "node_modules/@angular/cli": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.1.1.tgz", + "integrity": "sha512-Kzx+aUkAi8wx6m2e34Ekvyj9U46w7A3CHn6Zv+//TeplQitoMAzBOE8OiFVEcGJpi5gQ+NLDu0egfh2D+CC+ug==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1401.1", + "@angular-devkit/core": "14.1.1", + "@angular-devkit/schematics": "14.1.1", + "@schematics/angular": "14.1.1", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "debug": "4.3.4", + "ini": "3.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "npm-package-arg": "9.1.0", + "npm-pick-manifest": "7.0.1", + "open": "8.4.0", + "ora": "5.4.1", + "pacote": "13.6.1", + "resolve": "1.22.1", + "semver": "7.3.7", + "symbol-observable": "4.0.0", + "uuid": "8.3.2", + "yargs": "17.5.1" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/common": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.1.1.tgz", + "integrity": "sha512-neFCnrIrGOuj3oOFBTLi4QrdI4fgKQprVLUEyL5LhCQ5R0K/F+gh61ovi7nT4XYnv41p4eqtG81YNMtVXH49pg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.1.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-14.1.1.tgz", + "integrity": "sha512-OT3cFfbHzLpl2M9qpO74oysXDkkKwlO66i4vlASMGf1/Qh+4UBh3iN6bls/ZbYZsl8bCr9zf0fL7c160e1uMcA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.1.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-14.1.1.tgz", + "integrity": "sha512-ERphqFDdN5u1XCZNV21DS0J9/WFZP/P4L4LQTjpEwbO/lDhzxaRnRnLOR6vGcetEHhRHMa0+OL8rrvNy6GwLmw==", + "dependencies": { + "@babel/core": "^7.17.2", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.26.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/main-ngcc.js" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/compiler": "14.1.1", + "typescript": ">=4.6.2 <4.8" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular/core": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.1.1.tgz", + "integrity": "sha512-l3ms6/jxIUIeuCkXhz5nhRb94KLQ6wv9+B4lE0aJXcgHTqOmhc/ZIacT51LCjvVcok/vczF3f7w71Ii8b10yKQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.11.4" + } + }, + "node_modules/@angular/forms": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-14.1.1.tgz", + "integrity": "sha512-s4VuaivJ+s2hLlE8CMHLsAAmJNV/03EgBEJQV7rt1H0ogHs0jB/zlkzVw5K5bynCFkfIeDbwd6RvxzWhwE+ong==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.1.1", + "@angular/core": "14.1.1", + "@angular/platform-browser": "14.1.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/language-service": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-14.1.1.tgz", + "integrity": "sha512-bpUXsScvqzBodt94hEZdAHRZwHf3qqzadBcpTxJLoeJm69Q6hU1OdT7Id8wQfDESREUfbux12E1yVv7rEoIhJg==", + "dev": true, + "engines": { + "node": "^14.15.0 || >=16.10.0" + } + }, + "node_modules/@angular/localize": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-14.1.1.tgz", + "integrity": "sha512-SUGTDJYcJoSJWaFcG12njbfnFreZZRnEr3Rs211wD3VSu4UJXrpP1hx2M/FoaHLZkcgpSSfkg/QHLDLYJLRL9Q==", + "dependencies": { + "@babel/core": "7.18.9", + "glob": "8.0.3", + "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": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/compiler": "14.1.1", + "@angular/compiler-cli": "14.1.1" + } + }, + "node_modules/@angular/localize/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==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@angular/localize/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@angular/localize/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/platform-browser": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.1.1.tgz", + "integrity": "sha512-7yXr2GUiI1sD3kmKcWkHwlpmsRyA3WhwJqvjvMPQK4RD8ZeJ5LTOD6nQ4hz1kP19dfzpBDV/k9wusYDlmWtqcw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/animations": "14.1.1", + "@angular/common": "14.1.1", + "@angular/core": "14.1.1" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.1.1.tgz", + "integrity": "sha512-rD5KIWdxYRO2R0oGW6Ipt5pi+Cufws1704QBXhL52uaSJI6Ms7E7jvuwLG2SCJS0FJ+hdYLcuQZiQH+wUuyARA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.1.1", + "@angular/compiler": "14.1.1", + "@angular/core": "14.1.1", + "@angular/platform-browser": "14.1.1" + } + }, + "node_modules/@angular/router": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-14.1.1.tgz", + "integrity": "sha512-yWgy4NXp0e4XxOXRwaY6YSlOseXoLCVp7jKeBGAqJXypT+HtWXwpWE12vPC8EvkdPLyrf+EuH3kNbSbLfUNtbw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/common": "14.1.1", + "@angular/core": "14.1.1", + "@angular/platform-browser": "14.1.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.9.tgz", + "integrity": "sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.9", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz", + "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", + "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", + "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", + "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", + "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz", + "integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "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.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz", + "integrity": "sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", + "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "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.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", + "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "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-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", + "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-identifier": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", + "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", + "dev": true, + "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-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.6.tgz", + "integrity": "sha512-8uRHk9ZmRSnWqUgyae249EJZ94b0yAGLBIqzZzl+0iEdbno55Pmlt/32JZsHwXD9k/uZj18Aqqk35wBX4CBTXA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-polyfill-corejs2": "^0.3.1", + "babel-plugin-polyfill-corejs3": "^0.5.2", + "babel-plugin-polyfill-regenerator": "^0.3.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", + "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "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-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.6.tgz", + "integrity": "sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.6", + "@babel/plugin-proposal-async-generator-functions": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.6", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.6", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.6", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.6", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.6", + "@babel/plugin-transform-classes": "^7.18.6", + "@babel/plugin-transform-computed-properties": "^7.18.6", + "@babel/plugin-transform-destructuring": "^7.18.6", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.6", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.6", + "@babel/plugin-transform-function-name": "^7.18.6", + "@babel/plugin-transform-literals": "^7.18.6", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.6", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.6", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.6", + "@babel/plugin-transform-typeof-symbol": "^7.18.6", + "@babel/plugin-transform-unicode-escapes": "^7.18.6", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.6", + "babel-plugin-polyfill-corejs2": "^0.3.1", + "babel-plugin-polyfill-corejs3": "^0.5.2", + "babel-plugin-polyfill-regenerator": "^0.3.1", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", + "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz", + "integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.11", + "@babel/types": "^7.18.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "dependencies": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "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, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.5.tgz", + "integrity": "sha512-Id/9wBT7FkgFzdEpiEWrsVd4ltDxN0rI0QS0SChbeQiSuux3z21SJCRLu6h2cvCEUmaRi+VD0mHFj+GJD4GFnw==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "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, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@fortawesome/angular-fontawesome": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.11.1.tgz", + "integrity": "sha512-Ngzm5MVxk76ZhYpPTNOI/mpYNz9bzwfBXC5L9mktLgOONjBuYBPVt+bH8lny8hNtDk0ppZzXsMN6CO7hckdfnw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@angular/core": "^14.0.0", + "@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", + "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "1.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", + "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz", + "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", + "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", + "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@ng-bootstrap/ng-bootstrap": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-13.0.0.tgz", + "integrity": "sha512-aumflJ24VVOQ6kIGmpaWmjqfreRsXOCf/l2nOxPO6Y+d7Pit6aZthyjO7F0bRMutv6n+B/ma18GKvhhBcMepUw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^14.1.0", + "@angular/core": "^14.1.0", + "@angular/forms": "^14.1.0", + "@angular/localize": "^14.1.0", + "@popperjs/core": "^2.10.2", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ng-select/ng-select": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-9.0.2.tgz", + "integrity": "sha512-xdNiz/kgkMWYW1qFtk/337xDk/cmfEbSVtTFxWIM2OnIX1XsQOnTlGiBYces1TsMfqS68HjAvljEkj8QIGN2Lg==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 12.20.0", + "npm": ">= 6.0.0" + }, + "peerDependencies": { + "@angular/common": "<15.0.0", + "@angular/core": "<15.0.0", + "@angular/forms": "<15.0.0" + } + }, + "node_modules/@ngtools/webpack": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.1.1.tgz", + "integrity": "sha512-pj8sN6jBIi2otTHE/CNQyy09Pmn8tGsZyFrSiNO147yjLnrOSJTeFuXfE2pYgrmcYgj0ybnK1zstt4bAKaL7/Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^14.0.0", + "typescript": ">=4.6.2 <4.8", + "webpack": "^5.54.0" + } + }, + "node_modules/@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=13.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz", + "integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@ngx-translate/core": ">=14.0.0", + "rxjs": "^6.5.3 || ^7.4.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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.1.tgz", + "integrity": "sha512-1Q0uzx6c/NVNGszePbr5Gc2riSU1zLpNlo/1YWntH+eaPmMgBssAW0qXofCVkpdj3ce4swZtlDYQu+NKiYcptg==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.1.tgz", + "integrity": "sha512-UU85F/T+F1oVn3IsB/L6k9zXIMpXBuUBE25QDH0SsURwT6IOBqkC7M16uqo2vVZIyji3X1K4XH9luip7YekH1A==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/git/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.0.tgz", + "integrity": "sha512-UR6D5f4KEGWJV6BGPH3Qb2EtgH+t+1XQ1Tt85c7qicN6cezzuHPdZwwAxqZr4JLtnQu0LZsTza/5gmNmSl8XLg==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", + "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "dev": true, + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.0.tgz", + "integrity": "sha512-e/QgLg7j2wSJp1/7JRl0GC8c7PMX+uYlA/1Tb+IDOLdSM4T7K1VQ9mm9IGU3WRtY5vEIObpqCLb3aCNCug18DA==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/run-script/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@schematics/angular": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-14.1.1.tgz", + "integrity": "sha512-oSRDDhzg/27RKrQRoz09yELyBtsAFYfR1f+uq41FcmHZFfwOA5mQaqN2CQ1gUFygUZfZgOWSc+wma3ACIrwbHA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "14.1.1", + "@angular-devkit/schematics": "14.1.1", + "jsonc-parser": "3.1.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", + "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.18.tgz", + "integrity": "sha512-CYOO2DsfoFnmYQ+tZyXsExePUomwvUhpSLEsM7kAJ5BSYNlom+5m3qZkxYrg2CoSfJ3tMv5NH02cB0y7GfjvaA==", + "dev": true + }, + "node_modules/@types/jasminewd2": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.10.tgz", + "integrity": "sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g==", + "dev": true, + "dependencies": { + "@types/jasmine": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "12.20.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", + "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "node_modules/@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/selenium-webdriver": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", + "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", + "dev": true + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.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 + }, + "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 + }, + "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 + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "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, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "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, + "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, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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, + "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, + "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, + "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, + "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" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.8.tgz", + "integrity": "sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.3", + "caniuse-lite": "^1.0.30001373", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-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, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", + "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + } + ] + }, + "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, + "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 + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "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, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "blocking-proxy": "built/lib/bin.js" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.10.3", + "raw-body": "2.5.1", + "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, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/body-parser/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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bonjour-service": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.13.tgz", + "integrity": "sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "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 + }, + "node_modules/bootstrap": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.1.tgz", + "integrity": "sha512-UQi3v2NpVPEi1n35dmRRzBJFlgvWHYwyem6yHhuT6afYF+sziEt46McRbT//kVXZ7b1YUYEVGdXEH74Nx3xzGA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", + "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + } + }, + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.1.tgz", + "integrity": "sha512-VDKN+LHyCQXaaYZ7rA/qtkURU+/yYhviUdvqEv2LT6QPZU8jpyzEkEVAcKlKLt5dJ5BRp11ym8lo3NKLluEPLg==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001374", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz", + "integrity": "sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "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==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "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, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "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, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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 + }, + "node_modules/common": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/common/-/common-0.2.5.tgz", + "integrity": "sha512-bjuztiY8Wcz2V9dPpa0XdF0unGWqAGsnyKDGV7g6xDcaZfpufRrxV35qhuhjwv1h0jEPeiCHWKAQFLj702jGnA==" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "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 + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "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, + "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, + "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/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" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "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 + }, + "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, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/core-js-compat": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz", + "integrity": "sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.3", + "semver": "7.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "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, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + } + }, + "node_modules/critters/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/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, + "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/critters/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/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==", + "dev": true + }, + "node_modules/critters/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "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, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cssdb": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-6.6.3.tgz", + "integrity": "sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + }, + "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, + "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": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "dependencies": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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 + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "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": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?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" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.211", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.211.tgz", + "integrity": "sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A==" + }, + "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==" + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "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, + "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, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "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, + "engines": { + "node": ">=6" + } + }, + "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 + }, + "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, + "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, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz", + "integrity": "sha512-/TlVHhOaq7Yz8N1OJrjqM3Auzo5wjvHFLk+T8pIue+fhnhIMpfAzsG6PLVMbFveVxqD2WOp3QHei+52IMUNmCw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "esbuild-android-64": "0.14.49", + "esbuild-android-arm64": "0.14.49", + "esbuild-darwin-64": "0.14.49", + "esbuild-darwin-arm64": "0.14.49", + "esbuild-freebsd-64": "0.14.49", + "esbuild-freebsd-arm64": "0.14.49", + "esbuild-linux-32": "0.14.49", + "esbuild-linux-64": "0.14.49", + "esbuild-linux-arm": "0.14.49", + "esbuild-linux-arm64": "0.14.49", + "esbuild-linux-mips64le": "0.14.49", + "esbuild-linux-ppc64le": "0.14.49", + "esbuild-linux-riscv64": "0.14.49", + "esbuild-linux-s390x": "0.14.49", + "esbuild-netbsd-64": "0.14.49", + "esbuild-openbsd-64": "0.14.49", + "esbuild-sunos-64": "0.14.49", + "esbuild-windows-32": "0.14.49", + "esbuild-windows-64": "0.14.49", + "esbuild-windows-arm64": "0.14.49" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.49.tgz", + "integrity": "sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.49.tgz", + "integrity": "sha512-g2HGr/hjOXCgSsvQZ1nK4nW/ei8JUx04Li74qub9qWrStlysaVmadRyTVuW32FGIpLQyc5sUjjZopj49eGGM2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.49.tgz", + "integrity": "sha512-3rvqnBCtX9ywso5fCHixt2GBCUsogNp9DjGmvbBohh31Ces34BVzFltMSxJpacNki96+WIcX5s/vum+ckXiLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.49.tgz", + "integrity": "sha512-XMaqDxO846srnGlUSJnwbijV29MTKUATmOLyQSfswbK/2X5Uv28M9tTLUJcKKxzoo9lnkYPsx2o8EJcTYwCs/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.49.tgz", + "integrity": "sha512-NJ5Q6AjV879mOHFri+5lZLTp5XsO2hQ+KSJYLbfY9DgCu8s6/Zl2prWXVANYTeCDLlrIlNNYw8y34xqyLDKOmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.49.tgz", + "integrity": "sha512-lFLtgXnAc3eXYqj5koPlBZvEbBSOSUbWO3gyY/0+4lBdRqELyz4bAuamHvmvHW5swJYL7kngzIZw6kdu25KGOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.49.tgz", + "integrity": "sha512-zTTH4gr2Kb8u4QcOpTDVn7Z8q7QEIvFl/+vHrI3cF6XOJS7iEI1FWslTo3uofB2+mn6sIJEQD9PrNZKoAAMDiA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.49.tgz", + "integrity": "sha512-hYmzRIDzFfLrB5c1SknkxzM8LdEUOusp6M2TnuQZJLRtxTgyPnZZVtyMeCLki0wKgYPXkFsAVhi8vzo2mBNeTg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.49.tgz", + "integrity": "sha512-iE3e+ZVv1Qz1Sy0gifIsarJMQ89Rpm9mtLSRtG3AH0FPgAzQ5Z5oU6vYzhc/3gSPi2UxdCOfRhw2onXuFw/0lg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.49.tgz", + "integrity": "sha512-KLQ+WpeuY+7bxukxLz5VgkAAVQxUv67Ft4DmHIPIW+2w3ObBPQhqNoeQUHxopoW/aiOn3m99NSmSV+bs4BSsdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.49.tgz", + "integrity": "sha512-n+rGODfm8RSum5pFIqFQVQpYBw+AztL8s6o9kfx7tjfK0yIGF6tm5HlG6aRjodiiKkH2xAiIM+U4xtQVZYU4rA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.49.tgz", + "integrity": "sha512-WP9zR4HX6iCBmMFH+XHHng2LmdoIeUmBpL4aL2TR8ruzXyT4dWrJ5BSbT8iNo6THN8lod6GOmYDLq/dgZLalGw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.49.tgz", + "integrity": "sha512-h66ORBz+Dg+1KgLvzTVQEA1LX4XBd1SK0Fgbhhw4akpG/YkN8pS6OzYI/7SGENiN6ao5hETRDSkVcvU9NRtkMQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.49.tgz", + "integrity": "sha512-DhrUoFVWD+XmKO1y7e4kNCqQHPs6twz6VV6Uezl/XHYGzM60rBewBF5jlZjG0nCk5W/Xy6y1xWeopkrhFFM0sQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.49.tgz", + "integrity": "sha512-BXaUwFOfCy2T+hABtiPUIpWjAeWK9P8O41gR4Pg73hpzoygVGnj0nI3YK4SJhe52ELgtdgWP/ckIkbn2XaTxjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.49.tgz", + "integrity": "sha512-lP06UQeLDGmVPw9Rg437Btu6J9/BmyhdoefnQ4gDEJTtJvKtQaUcOQrhjTq455ouZN4EHFH1h28WOJVANK41kA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.49.tgz", + "integrity": "sha512-4c8Zowp+V3zIWje329BeLbGh6XI9c/rqARNaj5yPHdC61pHI9UNdDxT3rePPJeWcEZVKjkiAS6AP6kiITp7FSw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.14.49.tgz", + "integrity": "sha512-5ddzZv8M3WI1fWZ5rEfK5cSA9swlWJcceKgqjKLLERC7FnlNW50kF7hxhpkyC0Z/4w7Xeyt3yUJ9QWNMDXLk2Q==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.49.tgz", + "integrity": "sha512-q7Rb+J9yHTeKr9QTPDYkqfkEj8/kcKz9lOabDuvEXpXuIcosWCJgo5Z7h/L4r7rbtTH4a8U2FGKb6s1eeOHmJA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.49.tgz", + "integrity": "sha512-+Cme7Ongv0UIUTniPqfTX6mJ8Deo7VXw9xN0yJEN1lQMHDppTNmKwAM3oGbD/Vqff+07K2gN0WfNkMohmG+dVw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.49.tgz", + "integrity": "sha512-v+HYNAXzuANrCbbLFJ5nmO3m5y2PGZWLe3uloAkLt87aXiO2mZr3BTmacZdjwNkNEHuH3bNtN8cak+mzVjVPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "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, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "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, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "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, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "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" + } + }, + "node_modules/express/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 + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "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, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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 + }, + "node_modules/express/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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/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" + } + ] + }, + "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, + "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 + }, + "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, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "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 + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "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.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "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 + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "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, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "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, + "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, + "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": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "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, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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==", + "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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "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/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "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 + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-validator/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, + "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/har-validator/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 + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "dependencies": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "node_modules/hosted-git-info": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.0.0.tgz", + "integrity": "sha512-rRnjWu0Bxj+nIfUOkz0695C0H6tRrN5iYIzYejb0tDEefe2AekHu/U5Kn9pEie5vsJqpNQU02az7TGSH3qpz4Q==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "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/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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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 + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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 + }, + "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, + "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/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, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "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, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "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/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "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, + "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, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ignore-walk/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==" + }, + "node_modules/ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/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, + "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/inquirer/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/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==", + "dev": true + }, + "node_modules/inquirer/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/inquirer/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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 + }, + "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==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "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": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "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==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true, + "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": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", + "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/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, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/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, + "engines": { + "node": ">=6" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.1.tgz", + "integrity": "sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.8.0.tgz", + "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", + "dev": true + }, + "node_modules/jasmine-spec-reporter": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.2.tgz", + "integrity": "sha512-6gP1LbVgJ+d7PKksQBc2H0oDGNRQI3gKUsWlswKaQ2fif9X5gzhQcgM5+kiJGCQVurOG09jqNhk7payggyp5+g==", + "dev": true, + "dependencies": { + "colors": "1.4.0" + } + }, + "node_modules/jasmine/node_modules/jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + }, + "node_modules/jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true, + "engines": { + "node": ">= 6.9.x" + } + }, + "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, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "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 + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "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" + ] + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "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/jszip/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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/karma": { + "version": "6.3.20", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", + "integrity": "sha512-HRNQhMuKOwKpjYlWiJP0DUrJOh+QjaI/DTaD8b9rEm4Il3tJ8MijutVZH4ts10LuUFst/CedwTS6vieCN8yTSw==", + "dev": true, + "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.4.1", + "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.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/mattlewis92" + } + }, + "node_modules/karma-jasmine": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.3.1.tgz", + "integrity": "sha512-Nxh7eX9mOQMyK0VSsMxdod+bcqrR/ikrmEiWj5M6fwuQ7oI+YEF1FckaDsWfs6TIpULm9f0fTKMjF7XcrvWyqQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^3.5.0" + }, + "engines": { + "node": ">= 8" + }, + "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, + "peerDependencies": { + "jasmine-core": ">=3.8", + "karma": ">=0.9", + "karma-jasmine": ">=1.1" + } + }, + "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, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "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, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "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, + "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/karma/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, + "engines": { + "node": ">=10" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "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": "11.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz", + "integrity": "sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "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, + "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, + "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, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "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, + "optional": true, + "engines": { + "node": ">=0.10.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, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "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 + }, + "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, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", + "integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "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 + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/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, + "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/log-symbols/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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==", + "dev": true + }, + "node_modules/log-symbols/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", + "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" + }, + "engines": { + "node": ">=8.0" + } + }, + "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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "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 + }, + "node_modules/make-fetch-happen": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.0.tgz", + "integrity": "sha512-OnEfCLofQVJ5zgKwGk55GaqosqKjaR6khQlJY3dBAA+hM25Bc5CmX5rKUfVut+rYA3uidA7zb7AvcglU87rPRg==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.7.tgz", + "integrity": "sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "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, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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 + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.0.tgz", + "integrity": "sha512-H9U4UVBGXEyyWJnqYDCLp1PwD8XIkJ4akNHp1aGVI+2Ym7wQMlxDKi4IB4JbmyU+pl9pEs/cVrK6cOuvmbK4Sg==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz", + "integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "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, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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 + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "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, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.1.0.tgz", + "integrity": "sha512-HkmN0ZpQJU7FLbJauJTHkHlSVAXlNGDAzH/VYFZGDOnFyn/Na3GlNJfkudmufOdS6/jNFhy88ObzL7ERz9es1g==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.0.tgz", + "integrity": "sha512-m+GL22VXJKkKbw62ZaBBjv8u6IE3UI4Mh5QakIqs3fWiKe0Xyi6L97hakwZK41/LD4R/2ly71Bayx0NLMwLA/g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "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==", + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-install-checks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", + "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "node_modules/npm-package-arg": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.0.tgz", + "integrity": "sha512-4J0GL+u2Nh6OnhvUKXRr2ZMG4lR8qtLp+kv7UiV00Y+nGiSxtttCyIRHCt5L5BNkXQld/RceYItau3MDOoGiBw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.1.tgz", + "integrity": "sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^1.1.2", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-packlist/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm-packlist/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm-packlist/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-pick-manifest": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz", + "integrity": "sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.0.tgz", + "integrity": "sha512-10LJQ/1+VhKrZjIuY9I/+gQTvumqqlgnsCufoXETHAPFTS3+M+Z5CFhZRDHGavmJ6rOye3UvNga88vl8n1r6gg==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "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, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "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 + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/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, + "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/ora/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/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==", + "dev": true + }, + "node_modules/ora/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "13.6.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.1.tgz", + "integrity": "sha512-L+2BI1ougAPsFjXRyBhcKmfT016NscRFLv6Pz5EiNf1CCFJFU0pSKKQwsZTyAQB+sTuUL4TyFyp6J1Ork3dOqw==", + "dev": true, + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "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, + "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-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, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.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, + "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, + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "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, + "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 + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "dependencies": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0" + }, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.8", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.8.tgz", + "integrity": "sha512-8rbj8kVu00RQh2fQF81oBqtduiANu4MIxhyf0HbbStgPtnFlWn0yiaYTpLHrPnJbffVY1s9apWsIoVZcc68FxA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "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, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.1.10.tgz", + "integrity": "sha512-lqd7LXCq0gWc0wKXtoKDru5wEUNjm3OryLVNRZ8OnW8km6fSNUuFrjEhU3nklxXE2jvd4qrox566acgh+xQt8w==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", + "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.7.2.tgz", + "integrity": "sha512-1q0ih7EDsZmCb/FMDRvosna7Gsbdx8CvYO5hYT120hcp2ZAuOHpSzibujZ4JpIUcAC02PG6b+eftxqjTFh5BNA==", + "dev": true, + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.0.4", + "@csstools/postcss-color-function": "^1.1.0", + "@csstools/postcss-font-format-keywords": "^1.0.0", + "@csstools/postcss-hwb-function": "^1.0.1", + "@csstools/postcss-ic-unit": "^1.0.0", + "@csstools/postcss-is-pseudo-class": "^2.0.6", + "@csstools/postcss-normalize-display-values": "^1.0.0", + "@csstools/postcss-oklab-function": "^1.1.0", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.1", + "@csstools/postcss-unset-value": "^1.0.1", + "autoprefixer": "^10.4.7", + "browserslist": "^4.21.0", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^6.6.3", + "postcss-attribute-case-insensitive": "^5.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.3", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.0", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.8", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.4", + "postcss-double-position-gradients": "^3.1.1", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.3", + "postcss-image-set-function": "^4.0.6", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.0", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.9", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.3", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.4", + "postcss-pseudo-class-any-link": "^7.1.5", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "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 + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.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 + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "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, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protractor": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", + "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", + "dev": true, + "dependencies": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.1.7", + "yargs": "^15.3.1" + }, + "bin": { + "protractor": "bin/protractor", + "webdriver-manager": "bin/webdriver-manager" + }, + "engines": { + "node": ">=10.13.x" + } + }, + "node_modules/protractor/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/protractor/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/protractor/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==", + "dev": true + }, + "node_modules/protractor/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/protractor/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/protractor/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, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/wrap-ansi/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/protractor/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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/protractor/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "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, + "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, + "optional": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "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, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "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" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "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-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-package-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.1.tgz", + "integrity": "sha512-MALHuNgYWdGW3gKzuNMuYtcSSZbGQm94fAp16xt8VsYTLBjUSc55bLMKe6gzpWue0Tfi6CBgwCSdDAqutGDhMg==", + "dev": true, + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-package-json/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/read-package-json/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "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 + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "node_modules/regexpu-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "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==" + }, + "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 + }, + "node_modules/sass": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz", + "integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.0.2.tgz", + "integrity": "sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/saucelabs/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/saucelabs/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/saucelabs/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/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, + "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/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/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 + }, + "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 + }, + "node_modules/selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "dependencies": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "engines": { + "node": ">= 6.9.0" + } + }, + "node_modules/selenium-webdriver/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/selenium-webdriver/node_modules/tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/selfsigned": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "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, + "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 + }, + "node_modules/send/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, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/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==", + "dev": true + }, + "node_modules/send/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, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "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, + "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, + "dependencies": { + "ms": "2.0.0" + } + }, + "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, + "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 + }, + "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 + }, + "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 + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", + "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "node_modules/socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", + "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.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, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "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, + "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, + "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.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", + "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", + "dev": true, + "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, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/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" + } + ] + }, + "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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylus": { + "version": "0.58.1", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.58.1.tgz", + "integrity": "sha512-AYiCHm5ogczdCPMfe9aeQa4NklB2gcf4D/IhzYPddJjTgPc+k4D/EVE0yfQbZD43MHP3lPy+8NZ9fcFxkrgs/w==", + "dev": true, + "dependencies": { + "css": "^3.0.0", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stylus-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.0.0.tgz", + "integrity": "sha512-WTbtLrNfOfLgzTaR9Lj/BPhQroKk/LC1hfTXSUbrxmxgfUo3Y3LpmKRVA2R1XbjvTAvOfaian9vOyfv1z99E+A==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "klona": "^2.0.5", + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "stylus": ">=0.52.4", + "webpack": "^5.0.0" + } + }, + "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==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "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.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", + "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.7", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.7.2" + }, + "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/terser-webpack-plugin/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, + "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/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/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 + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "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 + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "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==", + "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, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "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, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-node/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/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, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslint/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "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, + "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, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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 + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "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, + "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.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "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, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", + "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "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, + "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, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "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, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "dependencies": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/webdriver-manager": { + "version": "12.1.8", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.8.tgz", + "integrity": "sha512-qJR36SXG2VwKugPcdwhaqcLQOD7r8P2Xiv9sfNbfZrKBnX243iAkOueX1yAmeNgIKhJ3YAT/F2gq6IiEZzahsg==", + "dev": true, + "dependencies": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + }, + "bin": { + "webdriver-manager": "bin/webdriver-manager" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/webdriver-manager/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/webdriver-manager/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/webdriver-manager/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webdriver-manager/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack": { + "version": "5.73.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.73.0.tgz", + "integrity": "sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.4.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.9.3", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.3.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-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz", + "integrity": "sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "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": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "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, + "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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/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 + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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, + "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, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "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==", + "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/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/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==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "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/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "engines": { + "node": ">=4.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==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dependencies": { + "cliui": "^7.0.2", + "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.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/zone.js": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz", + "integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==", + "dependencies": { + "tslib": "^2.0.0" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@angular-devkit/architect": { + "version": "0.1401.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1401.1.tgz", + "integrity": "sha512-HIFrIwbjfXCOjbGlMpHzG3oQG0nM1opaFSeKi+JjzTIb0jWq2s8sJfn4tGOZEJU8aKtDpNYMcW+N3F0grZdR8w==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.1.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/build-angular": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.1.1.tgz", + "integrity": "sha512-SlkWvTP6lrXOr5dOLtI56hSX7WuDznaUFHGfXpuwA+buiUgvigzO68VhKfsKn5I44J0PRpqXKPM7bxOXAOMe8Q==", + "dev": true, + "requires": { + "@ampproject/remapping": "2.2.0", + "@angular-devkit/architect": "0.1401.1", + "@angular-devkit/build-webpack": "0.1401.1", + "@angular-devkit/core": "14.1.1", + "@babel/core": "7.18.6", + "@babel/generator": "7.18.7", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.18.6", + "@babel/plugin-transform-async-to-generator": "7.18.6", + "@babel/plugin-transform-runtime": "7.18.6", + "@babel/preset-env": "7.18.6", + "@babel/runtime": "7.18.6", + "@babel/template": "7.18.6", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "14.1.1", + "ansi-colors": "4.1.3", + "babel-loader": "8.2.5", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.9.1", + "cacache": "16.1.1", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.1", + "esbuild": "0.14.49", + "esbuild-wasm": "0.14.49", + "glob": "8.0.3", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.0.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.0", + "mini-css-extract-plugin": "2.6.1", + "minimatch": "5.1.0", + "open": "8.4.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "6.0.1", + "piscina": "3.2.0", + "postcss": "8.4.14", + "postcss-import": "14.1.0", + "postcss-loader": "7.0.1", + "postcss-preset-env": "7.7.2", + "regenerator-runtime": "0.13.9", + "resolve-url-loader": "5.0.0", + "rxjs": "6.6.7", + "sass": "1.53.0", + "sass-loader": "13.0.2", + "semver": "7.3.7", + "source-map-loader": "4.0.0", + "source-map-support": "0.5.21", + "stylus": "0.58.1", + "stylus-loader": "7.0.0", + "terser": "5.14.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.4.0", + "webpack": "5.73.0", + "webpack-dev-middleware": "5.3.3", + "webpack-dev-server": "4.9.3", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", + "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helpers": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1401.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1401.1.tgz", + "integrity": "sha512-MY3KHLSRC6Ev4I9RLoAObyEoT95SYZSdnZQA+2WWcRXzrtCo48IfA1joojo6SLBd4k5uUisBs9aDK1NU8ugbQQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1401.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.1.1.tgz", + "integrity": "sha512-i5SiU/9xqKbhi5A2kq7ME5KWNXtVIlSLZ/HslGjsolZ4CO0LiZanywcE/HGL7681RVaWVeeSndmKQtqY3mPuNQ==", + "dev": true, + "requires": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + } + }, + "@angular-devkit/schematics": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.1.1.tgz", + "integrity": "sha512-9ymklxBm6ZxB4dvfsowyHQRx+DE7lQShDDMnwT2mPtH7SwbaLEUz02aL4W5BsuR6U1W+M181pZ4Igb3oq0AEoA==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.1.1", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + } + }, + "@angular/animations": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-14.1.1.tgz", + "integrity": "sha512-/fXzJzr8Pr7/xpwErX9PjbIc790RF818WgW7SUNev6pN6UUq3gvjmPjDTdZBZfiAZWY49nBLibPo2FvmyCP7tg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/cli": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.1.1.tgz", + "integrity": "sha512-Kzx+aUkAi8wx6m2e34Ekvyj9U46w7A3CHn6Zv+//TeplQitoMAzBOE8OiFVEcGJpi5gQ+NLDu0egfh2D+CC+ug==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1401.1", + "@angular-devkit/core": "14.1.1", + "@angular-devkit/schematics": "14.1.1", + "@schematics/angular": "14.1.1", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "debug": "4.3.4", + "ini": "3.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.1.0", + "npm-package-arg": "9.1.0", + "npm-pick-manifest": "7.0.1", + "open": "8.4.0", + "ora": "5.4.1", + "pacote": "13.6.1", + "resolve": "1.22.1", + "semver": "7.3.7", + "symbol-observable": "4.0.0", + "uuid": "8.3.2", + "yargs": "17.5.1" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@angular/common": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.1.1.tgz", + "integrity": "sha512-neFCnrIrGOuj3oOFBTLi4QrdI4fgKQprVLUEyL5LhCQ5R0K/F+gh61ovi7nT4XYnv41p4eqtG81YNMtVXH49pg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-14.1.1.tgz", + "integrity": "sha512-OT3cFfbHzLpl2M9qpO74oysXDkkKwlO66i4vlASMGf1/Qh+4UBh3iN6bls/ZbYZsl8bCr9zf0fL7c160e1uMcA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler-cli": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-14.1.1.tgz", + "integrity": "sha512-ERphqFDdN5u1XCZNV21DS0J9/WFZP/P4L4LQTjpEwbO/lDhzxaRnRnLOR6vGcetEHhRHMa0+OL8rrvNy6GwLmw==", + "requires": { + "@babel/core": "^7.17.2", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.11.0", + "magic-string": "^0.26.0", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "sourcemap-codec": "^1.4.8", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz", + "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.10", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.10", + "@babel/types": "^7.18.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@angular/core": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.1.1.tgz", + "integrity": "sha512-l3ms6/jxIUIeuCkXhz5nhRb94KLQ6wv9+B4lE0aJXcgHTqOmhc/ZIacT51LCjvVcok/vczF3f7w71Ii8b10yKQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/forms": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-14.1.1.tgz", + "integrity": "sha512-s4VuaivJ+s2hLlE8CMHLsAAmJNV/03EgBEJQV7rt1H0ogHs0jB/zlkzVw5K5bynCFkfIeDbwd6RvxzWhwE+ong==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/language-service": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-14.1.1.tgz", + "integrity": "sha512-bpUXsScvqzBodt94hEZdAHRZwHf3qqzadBcpTxJLoeJm69Q6hU1OdT7Id8wQfDESREUfbux12E1yVv7rEoIhJg==", + "dev": true + }, + "@angular/localize": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-14.1.1.tgz", + "integrity": "sha512-SUGTDJYcJoSJWaFcG12njbfnFreZZRnEr3Rs211wD3VSu4UJXrpP1hx2M/FoaHLZkcgpSSfkg/QHLDLYJLRL9Q==", + "requires": { + "@babel/core": "7.18.9", + "glob": "8.0.3", + "yargs": "^17.2.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@angular/platform-browser": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.1.1.tgz", + "integrity": "sha512-7yXr2GUiI1sD3kmKcWkHwlpmsRyA3WhwJqvjvMPQK4RD8ZeJ5LTOD6nQ4hz1kP19dfzpBDV/k9wusYDlmWtqcw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.1.1.tgz", + "integrity": "sha512-rD5KIWdxYRO2R0oGW6Ipt5pi+Cufws1704QBXhL52uaSJI6Ms7E7jvuwLG2SCJS0FJ+hdYLcuQZiQH+wUuyARA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/router": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-14.1.1.tgz", + "integrity": "sha512-yWgy4NXp0e4XxOXRwaY6YSlOseXoLCVp7jKeBGAqJXypT+HtWXwpWE12vPC8EvkdPLyrf+EuH3kNbSbLfUNtbw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz", + "integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==" + }, + "@babel/core": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.9.tgz", + "integrity": "sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.9", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helpers": "^7.18.9", + "@babel/parser": "^7.18.9", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.18.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.7.tgz", + "integrity": "sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==", + "dev": true, + "requires": { + "@babel/types": "^7.18.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz", + "integrity": "sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==", + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz", + "integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz", + "integrity": "sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz", + "integrity": "sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz", + "integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==", + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz", + "integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz", + "integrity": "sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", + "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==" + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" + }, + "@babel/helper-wrap-function": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz", + "integrity": "sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.18.9", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10" + }, + "dependencies": { + "@babel/template": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" + } + } + } + }, + "@babel/helpers": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", + "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", + "requires": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz", + "integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "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.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz", + "integrity": "sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", + "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.8", + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", + "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", + "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz", + "integrity": "sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz", + "integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", + "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", + "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz", + "integrity": "sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-validator-identifier": "^7.18.6", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz", + "integrity": "sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", + "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.6.tgz", + "integrity": "sha512-8uRHk9ZmRSnWqUgyae249EJZ94b0yAGLBIqzZzl+0iEdbno55Pmlt/32JZsHwXD9k/uZj18Aqqk35wBX4CBTXA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "babel-plugin-polyfill-corejs2": "^0.3.1", + "babel-plugin-polyfill-corejs3": "^0.5.2", + "babel-plugin-polyfill-regenerator": "^0.3.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz", + "integrity": "sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.6.tgz", + "integrity": "sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.6", + "@babel/plugin-proposal-async-generator-functions": "^7.18.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.6", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.6", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.18.6", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.6", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.18.6", + "@babel/plugin-transform-classes": "^7.18.6", + "@babel/plugin-transform-computed-properties": "^7.18.6", + "@babel/plugin-transform-destructuring": "^7.18.6", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.6", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.6", + "@babel/plugin-transform-function-name": "^7.18.6", + "@babel/plugin-transform-literals": "^7.18.6", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.18.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.6", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.18.6", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.6", + "@babel/plugin-transform-typeof-symbol": "^7.18.6", + "@babel/plugin-transform-unicode-escapes": "^7.18.6", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.18.6", + "babel-plugin-polyfill-corejs2": "^0.3.1", + "babel-plugin-polyfill-corejs3": "^0.5.2", + "babel-plugin-polyfill-regenerator": "^0.3.1", + "core-js-compat": "^3.22.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz", + "integrity": "sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/traverse": { + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz", + "integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.10", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.11", + "@babel/types": "^7.18.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.18.12", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz", + "integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==", + "requires": { + "@babel/types": "^7.18.10", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/types": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz", + "integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==", + "requires": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + }, + "@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 + }, + "@csstools/postcss-cascade-layers": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.5.tgz", + "integrity": "sha512-Id/9wBT7FkgFzdEpiEWrsVd4ltDxN0rI0QS0SChbeQiSuux3z21SJCRLu6h2cvCEUmaRi+VD0mHFj+GJD4GFnw==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + } + }, + "@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "dev": true, + "requires": {} + }, + "@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "requires": {} + }, + "@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 + }, + "@fortawesome/angular-fontawesome": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.11.1.tgz", + "integrity": "sha512-Ngzm5MVxk76ZhYpPTNOI/mpYNz9bzwfBXC5L9mktLgOONjBuYBPVt+bH8lny8hNtDk0ppZzXsMN6CO7hckdfnw==", + "requires": { + "tslib": "^2.4.0" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", + "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", + "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz", + "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", + "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", + "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + } + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@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 + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@ng-bootstrap/ng-bootstrap": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-13.0.0.tgz", + "integrity": "sha512-aumflJ24VVOQ6kIGmpaWmjqfreRsXOCf/l2nOxPO6Y+d7Pit6aZthyjO7F0bRMutv6n+B/ma18GKvhhBcMepUw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ng-select/ng-select": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-9.0.2.tgz", + "integrity": "sha512-xdNiz/kgkMWYW1qFtk/337xDk/cmfEbSVtTFxWIM2OnIX1XsQOnTlGiBYces1TsMfqS68HjAvljEkj8QIGN2Lg==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@ngtools/webpack": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.1.1.tgz", + "integrity": "sha512-pj8sN6jBIi2otTHE/CNQyy09Pmn8tGsZyFrSiNO147yjLnrOSJTeFuXfE2pYgrmcYgj0ybnK1zstt4bAKaL7/Q==", + "dev": true, + "requires": {} + }, + "@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ngx-translate/http-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz", + "integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@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, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@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 + }, + "@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, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.1.tgz", + "integrity": "sha512-1Q0uzx6c/NVNGszePbr5Gc2riSU1zLpNlo/1YWntH+eaPmMgBssAW0qXofCVkpdj3ce4swZtlDYQu+NKiYcptg==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-3.0.1.tgz", + "integrity": "sha512-UU85F/T+F1oVn3IsB/L6k9zXIMpXBuUBE25QDH0SsURwT6IOBqkC7M16uqo2vVZIyji3X1K4XH9luip7YekH1A==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "dependencies": { + "lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/move-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.0.tgz", + "integrity": "sha512-UR6D5f4KEGWJV6BGPH3Qb2EtgH+t+1XQ1Tt85c7qicN6cezzuHPdZwwAxqZr4JLtnQu0LZsTza/5gmNmSl8XLg==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/node-gyp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz", + "integrity": "sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz", + "integrity": "sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g==", + "dev": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.0.tgz", + "integrity": "sha512-e/QgLg7j2wSJp1/7JRl0GC8c7PMX+uYlA/1Tb+IDOLdSM4T7K1VQ9mm9IGU3WRtY5vEIObpqCLb3aCNCug18DA==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "peer": true + }, + "@schematics/angular": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-14.1.1.tgz", + "integrity": "sha512-oSRDDhzg/27RKrQRoz09yELyBtsAFYfR1f+uq41FcmHZFfwOA5mQaqN2CQ1gUFygUZfZgOWSc+wma3ACIrwbHA==", + "dev": true, + "requires": { + "@angular-devkit/core": "14.1.1", + "@angular-devkit/schematics": "14.1.1", + "jsonc-parser": "3.1.0" + } + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, + "@types/eslint": { + "version": "8.4.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", + "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.30", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz", + "integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.18.tgz", + "integrity": "sha512-CYOO2DsfoFnmYQ+tZyXsExePUomwvUhpSLEsM7kAJ5BSYNlom+5m3qZkxYrg2CoSfJ3tMv5NH02cB0y7GfjvaA==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.10.tgz", + "integrity": "sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true + }, + "@types/node": { + "version": "12.20.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz", + "integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", + "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", + "dev": true + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@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 + }, + "@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 + }, + "@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 + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "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, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "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, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "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, + "requires": { + "ajv": "^8.0.0" + } + }, + "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, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "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 + }, + "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, + "requires": { + "type-fest": "^0.21.3" + } + }, + "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 + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "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==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.8.tgz", + "integrity": "sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw==", + "dev": true, + "requires": { + "browserslist": "^4.21.3", + "caniuse-lite": "^1.0.30001373", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "babel-loader": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz", + "integrity": "sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.2", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz", + "integrity": "sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.2", + "core-js-compat": "^3.21.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", + "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "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 + }, + "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 + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "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 + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "body-parser": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "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 + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "bonjour-service": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.13.tgz", + "integrity": "sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "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 + }, + "bootstrap": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.1.tgz", + "integrity": "sha512-UQi3v2NpVPEi1n35dmRRzBJFlgvWHYwyem6yHhuT6afYF+sziEt46McRbT//kVXZ7b1YUYEVGdXEH74Nx3xzGA==", + "requires": {} + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "requires": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + } + }, + "browserstack": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", + "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + } + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cacache": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.1.tgz", + "integrity": "sha512-VDKN+LHyCQXaaYZ7rA/qtkURU+/yYhviUdvqEv2LT6QPZU8jpyzEkEVAcKlKLt5dJ5BRp11ym8lo3NKLluEPLg==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001374", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz", + "integrity": "sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "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, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "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==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "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 + }, + "common": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/common/-/common-0.2.5.tgz", + "integrity": "sha512-bjuztiY8Wcz2V9dPpa0XdF0unGWqAGsnyKDGV7g6xDcaZfpufRrxV35qhuhjwv1h0jEPeiCHWKAQFLj702jGnA==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "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 + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "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, + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "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 + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "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, + "requires": { + "is-what": "^3.14.1" + } + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "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, + "requires": { + "is-glob": "^4.0.3" + } + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "core-js-compat": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.24.1.tgz", + "integrity": "sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==", + "dev": true, + "requires": { + "browserslist": "^4.21.3", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "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, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "css-loader": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", + "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.7", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + } + }, + "css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "dev": true, + "requires": {} + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "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 + }, + "cssdb": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-6.6.3.tgz", + "integrity": "sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA==", + "dev": true + }, + "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 + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "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 + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "dev": true + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "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 + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.211", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.211.tgz", + "integrity": "sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A==" + }, + "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==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "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, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + } + }, + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "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, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "esbuild": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.49.tgz", + "integrity": "sha512-/TlVHhOaq7Yz8N1OJrjqM3Auzo5wjvHFLk+T8pIue+fhnhIMpfAzsG6PLVMbFveVxqD2WOp3QHei+52IMUNmCw==", + "dev": true, + "optional": true, + "requires": { + "esbuild-android-64": "0.14.49", + "esbuild-android-arm64": "0.14.49", + "esbuild-darwin-64": "0.14.49", + "esbuild-darwin-arm64": "0.14.49", + "esbuild-freebsd-64": "0.14.49", + "esbuild-freebsd-arm64": "0.14.49", + "esbuild-linux-32": "0.14.49", + "esbuild-linux-64": "0.14.49", + "esbuild-linux-arm": "0.14.49", + "esbuild-linux-arm64": "0.14.49", + "esbuild-linux-mips64le": "0.14.49", + "esbuild-linux-ppc64le": "0.14.49", + "esbuild-linux-riscv64": "0.14.49", + "esbuild-linux-s390x": "0.14.49", + "esbuild-netbsd-64": "0.14.49", + "esbuild-openbsd-64": "0.14.49", + "esbuild-sunos-64": "0.14.49", + "esbuild-windows-32": "0.14.49", + "esbuild-windows-64": "0.14.49", + "esbuild-windows-arm64": "0.14.49" + } + }, + "esbuild-android-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.49.tgz", + "integrity": "sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.49.tgz", + "integrity": "sha512-g2HGr/hjOXCgSsvQZ1nK4nW/ei8JUx04Li74qub9qWrStlysaVmadRyTVuW32FGIpLQyc5sUjjZopj49eGGM2g==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.49.tgz", + "integrity": "sha512-3rvqnBCtX9ywso5fCHixt2GBCUsogNp9DjGmvbBohh31Ces34BVzFltMSxJpacNki96+WIcX5s/vum+ckXiLYg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.49.tgz", + "integrity": "sha512-XMaqDxO846srnGlUSJnwbijV29MTKUATmOLyQSfswbK/2X5Uv28M9tTLUJcKKxzoo9lnkYPsx2o8EJcTYwCs/A==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.49.tgz", + "integrity": "sha512-NJ5Q6AjV879mOHFri+5lZLTp5XsO2hQ+KSJYLbfY9DgCu8s6/Zl2prWXVANYTeCDLlrIlNNYw8y34xqyLDKOmQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.49.tgz", + "integrity": "sha512-lFLtgXnAc3eXYqj5koPlBZvEbBSOSUbWO3gyY/0+4lBdRqELyz4bAuamHvmvHW5swJYL7kngzIZw6kdu25KGOA==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.49.tgz", + "integrity": "sha512-zTTH4gr2Kb8u4QcOpTDVn7Z8q7QEIvFl/+vHrI3cF6XOJS7iEI1FWslTo3uofB2+mn6sIJEQD9PrNZKoAAMDiA==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.49.tgz", + "integrity": "sha512-hYmzRIDzFfLrB5c1SknkxzM8LdEUOusp6M2TnuQZJLRtxTgyPnZZVtyMeCLki0wKgYPXkFsAVhi8vzo2mBNeTg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.49.tgz", + "integrity": "sha512-iE3e+ZVv1Qz1Sy0gifIsarJMQ89Rpm9mtLSRtG3AH0FPgAzQ5Z5oU6vYzhc/3gSPi2UxdCOfRhw2onXuFw/0lg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.49.tgz", + "integrity": "sha512-KLQ+WpeuY+7bxukxLz5VgkAAVQxUv67Ft4DmHIPIW+2w3ObBPQhqNoeQUHxopoW/aiOn3m99NSmSV+bs4BSsdA==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.49.tgz", + "integrity": "sha512-n+rGODfm8RSum5pFIqFQVQpYBw+AztL8s6o9kfx7tjfK0yIGF6tm5HlG6aRjodiiKkH2xAiIM+U4xtQVZYU4rA==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.49.tgz", + "integrity": "sha512-WP9zR4HX6iCBmMFH+XHHng2LmdoIeUmBpL4aL2TR8ruzXyT4dWrJ5BSbT8iNo6THN8lod6GOmYDLq/dgZLalGw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.49.tgz", + "integrity": "sha512-h66ORBz+Dg+1KgLvzTVQEA1LX4XBd1SK0Fgbhhw4akpG/YkN8pS6OzYI/7SGENiN6ao5hETRDSkVcvU9NRtkMQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.49.tgz", + "integrity": "sha512-DhrUoFVWD+XmKO1y7e4kNCqQHPs6twz6VV6Uezl/XHYGzM60rBewBF5jlZjG0nCk5W/Xy6y1xWeopkrhFFM0sQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.49.tgz", + "integrity": "sha512-BXaUwFOfCy2T+hABtiPUIpWjAeWK9P8O41gR4Pg73hpzoygVGnj0nI3YK4SJhe52ELgtdgWP/ckIkbn2XaTxjQ==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.49.tgz", + "integrity": "sha512-lP06UQeLDGmVPw9Rg437Btu6J9/BmyhdoefnQ4gDEJTtJvKtQaUcOQrhjTq455ouZN4EHFH1h28WOJVANK41kA==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.49.tgz", + "integrity": "sha512-4c8Zowp+V3zIWje329BeLbGh6XI9c/rqARNaj5yPHdC61pHI9UNdDxT3rePPJeWcEZVKjkiAS6AP6kiITp7FSw==", + "dev": true, + "optional": true + }, + "esbuild-wasm": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.14.49.tgz", + "integrity": "sha512-5ddzZv8M3WI1fWZ5rEfK5cSA9swlWJcceKgqjKLLERC7FnlNW50kF7hxhpkyC0Z/4w7Xeyt3yUJ9QWNMDXLk2Q==", + "dev": true + }, + "esbuild-windows-32": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.49.tgz", + "integrity": "sha512-q7Rb+J9yHTeKr9QTPDYkqfkEj8/kcKz9lOabDuvEXpXuIcosWCJgo5Z7h/L4r7rbtTH4a8U2FGKb6s1eeOHmJA==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.49.tgz", + "integrity": "sha512-+Cme7Ongv0UIUTniPqfTX6mJ8Deo7VXw9xN0yJEN1lQMHDppTNmKwAM3oGbD/Vqff+07K2gN0WfNkMohmG+dVw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.14.49", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.49.tgz", + "integrity": "sha512-v+HYNAXzuANrCbbLFJ5nmO3m5y2PGZWLe3uloAkLt87aXiO2mZr3BTmacZdjwNkNEHuH3bNtN8cak+mzVjVPfA==", + "dev": true, + "optional": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "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, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "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, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "express": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "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 + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "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 + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + }, + "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 + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "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, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "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 + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "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 + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "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, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "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==" + }, + "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==" + }, + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "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 + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "requires": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "hosted-git-info": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.0.0.tgz", + "integrity": "sha512-rRnjWu0Bxj+nIfUOkz0695C0H6tRrN5iYIzYejb0tDEefe2AekHu/U5Kn9pEie5vsJqpNQU02az7TGSH3qpz4Q==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true + } + } + }, + "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, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "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 + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "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 + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "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, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@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" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "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, + "requires": {} + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "ignore-walk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz", + "integrity": "sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "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, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.0.tgz", + "integrity": "sha512-TxYQaeNW/N8ymDvwAxPyRbhMBtnEwuvaTYpOQkFx1nSeusgezHniEc/l35Vo4iCq/mMiTJbpD7oYxN98hFlfmw==", + "dev": true + }, + "inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "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 + }, + "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==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "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==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "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 + }, + "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, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "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 + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", + "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "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, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "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 + } + } + }, + "istanbul-reports": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.1.tgz", + "integrity": "sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.8.0.tgz", + "integrity": "sha512-zl0nZWDrmbCiKns0NcjkFGYkVTGCPUgoHypTaj+G2AzaWus7QGoXARSlYsSle2VRpSdfJmM+hzmFKzQNhF2kHg==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.2.tgz", + "integrity": "sha512-6gP1LbVgJ+d7PKksQBc2H0oDGNRQI3gKUsWlswKaQ2fif9X5gzhQcgM5+kiJGCQVurOG09jqNhk7payggyp5+g==", + "dev": true, + "requires": { + "colors": "1.4.0" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "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, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "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 + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "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 + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + }, + "jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "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 + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "karma": { + "version": "6.3.20", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", + "integrity": "sha512-HRNQhMuKOwKpjYlWiJP0DUrJOh+QjaI/DTaD8b9rEm4Il3tJ8MijutVZH4ts10LuUFst/CedwTS6vieCN8yTSw==", + "dev": true, + "requires": { + "@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.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "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, + "requires": { + "minimist": "^1.2.6" + } + }, + "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 + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "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" + } + }, + "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 + } + } + }, + "karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", + "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^3.0.2", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.3.1.tgz", + "integrity": "sha512-Nxh7eX9mOQMyK0VSsMxdod+bcqrR/ikrmEiWj5M6fwuQ7oI+YEF1FckaDsWfs6TIpULm9f0fTKMjF7XcrvWyqQ==", + "dev": true, + "requires": { + "jasmine-core": "^3.5.0" + } + }, + "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, + "requires": {} + }, + "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, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "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 + }, + "klona": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz", + "integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==", + "dev": true + }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "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", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "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, + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + }, + "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, + "optional": true + } + } + }, + "less-loader": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.0.0.tgz", + "integrity": "sha512-9+LOWWjuoectIEx3zrfN83NAGxSUB5pWEabbbidVQVgZhN+wN68pOvuyirVlH1IK4VT1f3TmlyvAnCXh8O5KEw==", + "dev": true, + "requires": { + "klona": "^2.0.4" + } + }, + "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, + "requires": { + "webpack-sources": "^3.0.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "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 + }, + "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 + }, + "loader-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", + "integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "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 + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log4js": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", + "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", + "dev": true, + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.3" + } + }, + "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==", + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "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, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "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 + }, + "make-fetch-happen": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.0.tgz", + "integrity": "sha512-OnEfCLofQVJ5zgKwGk55GaqosqKjaR6khQlJY3dBAA+hM25Bc5CmX5rKUfVut+rYA3uidA7zb7AvcglU87rPRg==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz", + "integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==", + "dev": true + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memfs": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.7.tgz", + "integrity": "sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "requires": { + "mime-db": "1.51.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", + "integrity": "sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "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 + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minipass": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", + "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.0.tgz", + "integrity": "sha512-H9U4UVBGXEyyWJnqYDCLp1PwD8XIkJ4akNHp1aGVI+2Ym7wQMlxDKi4IB4JbmyU+pl9pEs/cVrK6cOuvmbK4Sg==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "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, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "needle": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz", + "integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==", + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "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 + }, + "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 + }, + "nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "optional": true, + "requires": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "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 + }, + "node-gyp": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.1.0.tgz", + "integrity": "sha512-HkmN0ZpQJU7FLbJauJTHkHlSVAXlNGDAzH/VYFZGDOnFyn/Na3GlNJfkudmufOdS6/jNFhy88ObzL7ERz9es1g==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "optional": true + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.0.tgz", + "integrity": "sha512-m+GL22VXJKkKbw62ZaBBjv8u6IE3UI4Mh5QakIqs3fWiKe0Xyi6L97hakwZK41/LD4R/2ly71Bayx0NLMwLA/g==", + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "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 + }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz", + "integrity": "sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.0.tgz", + "integrity": "sha512-4J0GL+u2Nh6OnhvUKXRr2ZMG4lR8qtLp+kv7UiV00Y+nGiSxtttCyIRHCt5L5BNkXQld/RceYItau3MDOoGiBw==", + "dev": true, + "requires": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + } + }, + "npm-packlist": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.1.tgz", + "integrity": "sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^1.1.2", + "npm-normalize-package-bin": "^1.0.1" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "npm-pick-manifest": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.1.tgz", + "integrity": "sha512-IA8+tuv8KujbsbLQvselW2XQgmXWS47t3CB0ZrzsRZ82DbDfkcFunOaPm4X7qNuhMfq+FmV7hQT4iFVpHqV7mg==", + "dev": true, + "requires": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.0.tgz", + "integrity": "sha512-10LJQ/1+VhKrZjIuY9I/+gQTvumqqlgnsCufoXETHAPFTS3+M+Z5CFhZRDHGavmJ6rOye3UvNga88vl8n1r6gg==", + "dev": true, + "requires": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "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, + "requires": { + "boolbase": "^1.0.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "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 + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "dependencies": { + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "13.6.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.1.tgz", + "integrity": "sha512-L+2BI1ougAPsFjXRyBhcKmfT016NscRFLv6Pz5EiNf1CCFJFU0pSKKQwsZTyAQB+sTuUL4TyFyp6J1Ork3dOqw==", + "dev": true, + "requires": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "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 + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "parse5-html-rewriting-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-6.0.1.tgz", + "integrity": "sha512-vwLQzynJVEfUlURxgnf51yAJDQTtVpNyGD8tKi2Za7m+akukNHxCcUQMAa/mUGLhCeicFdpy7Tlvj8ZNKadprg==", + "dev": true, + "requires": { + "parse5": "^6.0.1", + "parse5-sax-parser": "^6.0.1" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parse5-sax-parser": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", + "integrity": "sha512-kXX+5S81lgESA0LsDuGjAlBybImAChYRMT+/uKCEXFBFOeEhS52qUCydGhU3qLRD8D9DVjaUo821WK7DM4iCeg==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "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 + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "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 + }, + "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 + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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 + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "requires": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0", + "nice-napi": "^1.0.2" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "postcss": { + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-properties": { + "version": "12.1.8", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.8.tgz", + "integrity": "sha512-8rbj8kVu00RQh2fQF81oBqtduiANu4MIxhyf0HbbStgPtnFlWn0yiaYTpLHrPnJbffVY1s9apWsIoVZcc68FxA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.9" + } + }, + "postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "requires": {} + }, + "postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "dev": true, + "requires": {} + }, + "postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-import": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz", + "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "dev": true, + "requires": {} + }, + "postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dev": true, + "requires": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-loader": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", + "integrity": "sha512-VRviFEyYlLjctSM93gAZtcJJ/iSkPZ79zWbN/1fSH+NisBByEiVLqpdVDrPLVSi8DX0oJo12kL/GppTBdKVXiQ==", + "dev": true, + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.7" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "requires": {} + }, + "postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "dev": true, + "requires": {} + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "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, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-nesting": { + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.1.10.tgz", + "integrity": "sha512-lqd7LXCq0gWc0wKXtoKDru5wEUNjm3OryLVNRZ8OnW8km6fSNUuFrjEhU3nklxXE2jvd4qrox566acgh+xQt8w==", + "dev": true, + "requires": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-opacity-percentage": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz", + "integrity": "sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w==", + "dev": true + }, + "postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "requires": {} + }, + "postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-preset-env": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.7.2.tgz", + "integrity": "sha512-1q0ih7EDsZmCb/FMDRvosna7Gsbdx8CvYO5hYT120hcp2ZAuOHpSzibujZ4JpIUcAC02PG6b+eftxqjTFh5BNA==", + "dev": true, + "requires": { + "@csstools/postcss-cascade-layers": "^1.0.4", + "@csstools/postcss-color-function": "^1.1.0", + "@csstools/postcss-font-format-keywords": "^1.0.0", + "@csstools/postcss-hwb-function": "^1.0.1", + "@csstools/postcss-ic-unit": "^1.0.0", + "@csstools/postcss-is-pseudo-class": "^2.0.6", + "@csstools/postcss-normalize-display-values": "^1.0.0", + "@csstools/postcss-oklab-function": "^1.1.0", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.1", + "@csstools/postcss-unset-value": "^1.0.1", + "autoprefixer": "^10.4.7", + "browserslist": "^4.21.0", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^6.6.3", + "postcss-attribute-case-insensitive": "^5.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.3", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.0", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.8", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.4", + "postcss-double-position-gradients": "^3.1.1", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.3", + "postcss-image-set-function": "^4.0.6", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.0", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.1.9", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.3", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.4", + "postcss-pseudo-class-any-link": "^7.1.5", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "requires": {} + }, + "postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "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 + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "proc-log": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", + "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", + "dev": true + }, + "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 + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "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, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "protractor": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", + "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", + "dev": true, + "requires": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.1.7", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "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==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "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==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "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 + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "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, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "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 + } + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "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 + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "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 + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "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 + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "read-package-json": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.1.tgz", + "integrity": "sha512-MALHuNgYWdGW3gKzuNMuYtcSSZbGQm94fAp16xt8VsYTLBjUSc55bLMKe6gzpWue0Tfi6CBgwCSdDAqutGDhMg==", + "dev": true, + "requires": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "dependencies": { + "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, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "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 + }, + "regenerate-unicode-properties": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", + "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexpu-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.1.0.tgz", + "integrity": "sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.0.1", + "regjsgen": "^0.6.0", + "regjsparser": "^0.8.2", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.0.0" + } + }, + "regjsgen": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", + "dev": true + }, + "regjsparser": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", + "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "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 + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "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, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "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 + } + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "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==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.53.0.tgz", + "integrity": "sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.0.2.tgz", + "integrity": "sha512-BbiqbVmbfJaWVeOOAu2o7DhYWtcNmTfvroVgFXa6k2hHheMxNAeDHLNoDy/Q5aoaVlz0LH+MbMktKwm9vN/j8Q==", + "dev": true, + "requires": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + } + } + }, + "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 + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "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 + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "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, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "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, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "requires": { + "kind-of": "^6.0.2" + } + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "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 + }, + "socket.io": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", + "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.0" + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", + "dev": true + }, + "socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "dev": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "socks": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", + "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "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, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "streamroller": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", + "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", + "dev": true, + "requires": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + } + }, + "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, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "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 + } + } + }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "stylus": { + "version": "0.58.1", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.58.1.tgz", + "integrity": "sha512-AYiCHm5ogczdCPMfe9aeQa4NklB2gcf4D/IhzYPddJjTgPc+k4D/EVE0yfQbZD43MHP3lPy+8NZ9fcFxkrgs/w==", + "dev": true, + "requires": { + "css": "^3.0.0", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + } + }, + "stylus-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-7.0.0.tgz", + "integrity": "sha512-WTbtLrNfOfLgzTaR9Lj/BPhQroKk/LC1hfTXSUbrxmxgfUo3Y3LpmKRVA2R1XbjvTAvOfaian9vOyfv1z99E+A==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "klona": "^2.0.5", + "normalize-path": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "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 + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", + "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.7", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.7.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "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==", + "requires": { + "is-number": "^7.0.0" + } + }, + "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 + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "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 + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "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 + }, + "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, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "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 + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + }, + "ua-parser-js": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "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, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "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 + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", + "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "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 + }, + "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, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz", + "integrity": "sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webdriver-manager": { + "version": "12.1.8", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.8.tgz", + "integrity": "sha512-qJR36SXG2VwKugPcdwhaqcLQOD7r8P2Xiv9sfNbfZrKBnX243iAkOueX1yAmeNgIKhJ3YAT/F2gq6IiEZzahsg==", + "dev": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "webpack": { + "version": "5.73.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.73.0.tgz", + "integrity": "sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.4.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.9.3", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.3.1", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "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 + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + } + } + }, + "webpack-dev-server": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz", + "integrity": "sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.0.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "dependencies": { + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "requires": {} + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "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, + "requires": { + "typed-assert": "^1.0.8" + } + }, + "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, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "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 + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "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==", + "requires": { + "color-name": "~1.1.4" + } + }, + "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==" + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true, + "requires": {} + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "requires": { + "cliui": "^7.0.2", + "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.0.0" + } + }, + "yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==" + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, + "zone.js": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.4.tgz", + "integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==", + "requires": { + "tslib": "^2.0.0" + } + } + } +} diff --git a/frontend/projects/public/package.json b/frontend/projects/public/package.json new file mode 100644 index 0000000000..7bedc6b915 --- /dev/null +++ b/frontend/projects/public/package.json @@ -0,0 +1,60 @@ +{ + "name": "alfio-public-frontend", + "version": "0.0.0", + "scripts": { + "source-map-explorer": "ng build --source-map=true && npx source-map-explorer dist/alfio-public-frontend/**/*.js", + "ng": "ng", + "start": "ng serve", + "startProd": "ng serve --aot=true --configuration production", + "build": "ng build --aot=true --configuration production", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "^14.1.1", + "@angular/common": "^14.1.1", + "@angular/compiler": "^14.1.1", + "@angular/core": "^14.1.1", + "@angular/forms": "^14.1.1", + "@angular/localize": "^14.1.1", + "@angular/platform-browser": "^14.1.1", + "@angular/platform-browser-dynamic": "^14.1.1", + "@angular/router": "^14.1.1", + "@fortawesome/angular-fontawesome": "^0.11.1", + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-brands-svg-icons": "^5.15.3", + "@fortawesome/free-regular-svg-icons": "^5.15.3", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@ng-bootstrap/ng-bootstrap": "^13.0.0", + "@ng-select/ng-select": "^9.0.2", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "bootstrap": "^5.2.0", + "common": "^0.2.5", + "rxjs": "^6.6.3", + "tslib": "^2.0.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^14.1.1", + "@angular/cli": "^14.1.1", + "@angular/compiler-cli": "^14.1.1", + "@angular/language-service": "^14.1.1", + "@types/jasmine": "~2.8.8", + "@types/jasminewd2": "~2.0.3", + "@types/node": "^12.11.1", + "jasmine-core": "~3.8.0", + "jasmine-spec-reporter": "~5.0.0", + "karma": "~6.3.9", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~3.0.2", + "karma-jasmine": "~3.3.0", + "karma-jasmine-html-reporter": "^1.7.0", + "protractor": "~7.0.0", + "ts-node": "~7.0.0", + "tslint": "~6.1.0", + "typescript": "~4.7.4" + } +} diff --git a/frontend/projects/public/proxy.config.json b/frontend/projects/public/proxy.config.json new file mode 100644 index 0000000000..0beb1625a6 --- /dev/null +++ b/frontend/projects/public/proxy.config.json @@ -0,0 +1,36 @@ +{ + "/api": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + + "/file": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + + "/openid": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + + "/logout": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + + "/resources": { + "target": "http://localhost:8080", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + } +} diff --git a/frontend/projects/public/src/app/additional-field/additional-field.component.html b/frontend/projects/public/src/app/additional-field/additional-field.component.html new file mode 100644 index 0000000000..43940714cc --- /dev/null +++ b/frontend/projects/public/src/app/additional-field/additional-field.component.html @@ -0,0 +1,39 @@ +<ng-container [formGroup]="form"> + <div class="form-group" [formArrayName]="field.name"> + <label class="form-label" [attr.for]="ticketUUID + '-' + field.name" [id]="labelId" [attr.aria-hidden]="hideLabelForAssistiveTechnologies">{{labelValue + (field.required ? ' *' : '')}}</label> + <ng-container [ngSwitch]="field.type" *ngIf="editAllowed"> + <input *ngSwitchCase="'input:text'" [attr.id]="ticketUUID + '-' + field.name" type="text" [attr.placeholder]="placeholder" class="form-control" formControlName="0" appInvalidFeedback> + <input *ngSwitchCase="'input:tel'" [attr.id]="ticketUUID + '-' + field.name" type="tel" [attr.placeholder]="placeholder" class="form-control" formControlName="0" appInvalidFeedback> + <input *ngSwitchCase="'vat:eu'" [attr.id]="ticketUUID + '-' + field.name" type="text" [attr.placeholder]="placeholder" class="form-control" formControlName="0" appInvalidFeedback> + <textarea *ngSwitchCase="'textarea'" [attr.id]="ticketUUID + '-' + field.name" class="form-control" [attr.placeholder]="placeholder" formControlName="0" appInvalidFeedback></textarea> + <ng-container *ngSwitchCase="'country'"> + <ng-select *ngIf="!isMobile" [attr.id]="ticketUUID + '-' + field.name" formControlName="0" appInvalidFeedback [items]="countries" bindLabel="name" bindValue="isoCode" [inputAttrs]="{autocomplete: 'nope'}"> + <ng-template ng-notfound-tmp let-searchTerm="searchTerm"></ng-template> + </ng-select> + <select *ngIf="isMobile" [attr.id]="ticketUUID + '-' + field.name" class="custom-select" formControlName="0" appInvalidFeedback> + <option value=""></option> + <option *ngFor="let c of countries" [ngValue]="c.isoCode">{{c.name}}</option> + </select> + </ng-container> + <select *ngSwitchCase="'select'" [attr.id]="ticketUUID + '-' + field.name" class="form-select" formControlName="0" appInvalidFeedback> + <option value=""></option> + <option *ngFor="let c of field.restrictedValues" [ngValue]="c">{{getRestrictedValueLabel(c)}}</option> + </select> + <div *ngSwitchCase="'checkbox'" [attr.id]="ticketUUID + '-' + field.name" appInvalidFeedback [invalidFeedbackFieldName]="field.name" [invalidFeedbackForm]="form"> + <div *ngFor="let c of field.restrictedValues; let i = index; let isFirst = first" class="form-check"> + <input class="form-check-input" [attr.id]="ticketUUID + '-' + field.name + '-' + i" type="checkbox" [formControlName]="i" (ngModelChange)="selectedCheckBox(i, c, $event)">{{' '}} + <label class="form-check-label" [attr.for]="ticketUUID + '-' + field.name + '-' + i"><span class="sr-only" *ngIf="isFirst">{{labelValue}} </span>{{getRestrictedValueLabel(c)}}</label> + </div> + </div> + <div *ngSwitchCase="'radio'" [attr.id]="ticketUUID + '-' + field.name" appInvalidFeedback [invalidFeedbackFieldName]="field.name+'.0'" [invalidFeedbackForm]="form"> + <div *ngFor="let c of field.restrictedValues; let i = index" class="form-check"> + <input class="form-check-input" [attr.id]="ticketUUID + '-' + field.name + '-' + i" type="radio" [value]="c" formControlName="0"> + <label class="form-check-label" [attr.for]="ticketUUID + '-' + field.name + '-' + i">{{getRestrictedValueLabel(c)}}</label> + </div> + </div> + </ng-container> + <div class="form-control-plaintext" *ngIf="!editAllowed"> + {{ getRestrictedValueLabel(field.value) }} + </div> + </div> +</ng-container> diff --git a/frontend/projects/public/src/app/additional-field/additional-field.component.ts b/frontend/projects/public/src/app/additional-field/additional-field.component.ts new file mode 100644 index 0000000000..55b3f4012c --- /dev/null +++ b/frontend/projects/public/src/app/additional-field/additional-field.component.ts @@ -0,0 +1,98 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {UntypedFormArray, UntypedFormGroup} from '@angular/forms'; +import {AdditionalField} from '../model/ticket'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../shared/i18n.service'; +import {LocalizedCountry} from '../model/localized-country'; +import {Subscription} from 'rxjs'; +import {mobile} from '../shared/util'; + +@Component({ + selector: 'app-additional-field', + templateUrl: './additional-field.component.html' +}) +export class AdditionalFieldComponent implements OnInit, OnDestroy { + + @Input() + field: AdditionalField; + + @Input() + form: UntypedFormGroup; + + @Input() + ticketUUID: string; + + @Input() + ticketAcquired: boolean; + + + countries: LocalizedCountry[]; + + isMobile = mobile; + + private langChangeSub: Subscription; + + constructor(private translate: TranslateService, private i18nService: I18nService) { } + + ngOnInit(): void { + if (this.field.type === 'country') { + this.getCountries(); + this.langChangeSub = this.translate.onLangChange.subscribe(() => { + this.getCountries(); + }); + } + } + + ngOnDestroy(): void { + if (this.langChangeSub) { + this.langChangeSub.unsubscribe(); + } + } + + get labelValue(): string { + if (this.field && this.field.description && this.field.description[this.translate.currentLang]) { + return this.field.description[this.translate.currentLang].label; + } else { + return ''; + } + } + + get placeholder(): string { + if (this.field && this.field.description && this.field.description[this.translate.currentLang]) { + return this.field.description[this.translate.currentLang].placeholder; + } else { + return ''; + } + } + + get editAllowed(): boolean { + return !this.ticketAcquired || this.field.value == null || this.field.value.trim().length === 0 || this.field.editable; + } + + getCountries(): void { + this.i18nService.getCountries(this.translate.currentLang).subscribe(countries => { + this.countries = countries; + }); + } + + getRestrictedValueLabel(value: string): string { + if (this.field.description[this.translate.currentLang]) { + return this.field.description[this.translate.currentLang].restrictedValuesDescription[value] || value; + } else { + return value; + } + } + + selectedCheckBox(index: number, value: string, checked: boolean) { + const fa = this.form.get(this.field.name) as UntypedFormArray; + fa.controls[index].setValue(checked ? value : null, {emitEvent: false, emitViewToModelChange: false}); + } + + get labelId(): string { + return this.ticketUUID + '-' + this.field.name.replace(/[^a-zA-Z0-9]/g, '+') + '-label'; + } + + get hideLabelForAssistiveTechnologies(): boolean { + return this.field.type === 'checkbox'; + } +} diff --git a/frontend/projects/public/src/app/additional-service-quantity-selector/additional-service-quantity-selector.component.ts b/frontend/projects/public/src/app/additional-service-quantity-selector/additional-service-quantity-selector.component.ts new file mode 100644 index 0000000000..56781a38c4 --- /dev/null +++ b/frontend/projects/public/src/app/additional-service-quantity-selector/additional-service-quantity-selector.component.ts @@ -0,0 +1,24 @@ +import {Component, Input} from '@angular/core'; +import {AdditionalService} from '../model/additional-service'; +import {Event} from '../model/event'; +import {UntypedFormGroup} from '@angular/forms'; + +@Component({ + selector: 'app-additional-service-quantity-selector', + templateUrl: './additional-service-quantity-selector.html' +}) +export class AdditionalServiceQuantitySelectorComponent { + + @Input() + additionalService: AdditionalService; + + @Input() + validSelectionValues: number[]; + + @Input() + event: Event; + + @Input() + parentGroup: UntypedFormGroup; + +} diff --git a/frontend/projects/public/src/app/additional-service-quantity-selector/additional-service-quantity-selector.html b/frontend/projects/public/src/app/additional-service-quantity-selector/additional-service-quantity-selector.html new file mode 100644 index 0000000000..8edc795455 --- /dev/null +++ b/frontend/projects/public/src/app/additional-service-quantity-selector/additional-service-quantity-selector.html @@ -0,0 +1,20 @@ +<div [formGroup]="parentGroup"> + <label class="sr-only" [for]="'additional-'+additionalService.id+'-qty'">{{ (additionalService.fixPrice ? 'show-event.category.quantity' : 'show-event.additional.custom-amount') | translate }}</label> + <div *ngIf="additionalService.fixPrice && additionalService.supplementPolicy === 'MANDATORY_ONE_FOR_TICKET'" translate="show-event.mandatoryOneForTicket"></div> + <div *ngIf="additionalService.fixPrice && additionalService.supplementPolicy === 'OPTIONAL_UNLIMITED_AMOUNT'"> + <input type="number" min="0" step="1" class="form-control" placeholder="0" autocomplete="off" formControlName="quantity" [id]="'additional-'+additionalService.id+'-qty'"> + </div> + <div *ngIf="additionalService.fixPrice && [null, 'OPTIONAL_MAX_AMOUNT_PER_RESERVATION', 'OPTIONAL_MAX_AMOUNT_PER_TICKET'].indexOf(additionalService.supplementPolicy) >= 0 "> + <select class="form-select text-align-center" placeholder="0" autocomplete="off" formControlName="quantity" [id]="'additional-'+additionalService.id+'-qty'"> + <option *ngFor="let amount of validSelectionValues" [ngValue]="amount">{{amount}}</option> + </select> + </div> + <div *ngIf="!additionalService.fixPrice"> + <div class="input-group mb-3"> + <input type="number" min="0" step="any" class="form-control" value="0" formControlName="amount" [id]="'additional-'+additionalService.id+'-qty'"> + <div class="input-group-append"> + <span class="input-group-text append-currency">{{event.currency}}</span> + </div> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/additional-service/additional-service.component.html b/frontend/projects/public/src/app/additional-service/additional-service.component.html new file mode 100644 index 0000000000..50ed713c97 --- /dev/null +++ b/frontend/projects/public/src/app/additional-service/additional-service.component.html @@ -0,0 +1,14 @@ +<div [formGroup]="additionalServiceFormGroup"> + <app-item-card [currentLang]="translate.currentLang" [item]="additionalService" [parentFormGroup]="additionalServiceFormGroup"> + <span class="item-title">{{additionalService.title[translate.currentLang]}}</span> + <div class="item-price"> + <span *ngIf="additionalService.free" translate="common.free"></span> + <span *ngIf="!additionalService.free && additionalService.fixPrice"> + <app-price-tag [purchaseContext]="event" [formattedPrice]="additionalService.formattedFinalPrice" [discountedPrice]="additionalService.formattedDiscountedPrice" [showTaxDetails]="additionalService.vatApplies"></app-price-tag> + </span> + </div> + <div class="item-qty-selector"> + <app-additional-service-quantity-selector [parentGroup]="additionalServiceFormGroup" [event]="event" [additionalService]="additionalService" [validSelectionValues]="validSelectionValues"></app-additional-service-quantity-selector> + </div> + </app-item-card> +</div> diff --git a/frontend/projects/public/src/app/additional-service/additional-service.component.scss b/frontend/projects/public/src/app/additional-service/additional-service.component.scss new file mode 100644 index 0000000000..d752af5199 --- /dev/null +++ b/frontend/projects/public/src/app/additional-service/additional-service.component.scss @@ -0,0 +1,11 @@ + +.append-currency { + font-size: 12px; + padding-left: 4px; + padding-right: 4px; +} + +h3.additional-service-title { + font-size: 1rem; + font-weight: bold; +} diff --git a/frontend/projects/public/src/app/additional-service/additional-service.component.ts b/frontend/projects/public/src/app/additional-service/additional-service.component.ts new file mode 100644 index 0000000000..98b6046733 --- /dev/null +++ b/frontend/projects/public/src/app/additional-service/additional-service.component.ts @@ -0,0 +1,70 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {AdditionalService} from '../model/additional-service'; +import {TranslateService} from '@ngx-translate/core'; +import {UntypedFormArray, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; +import {Subscription} from 'rxjs'; +import {Event} from '../model/event'; + +@Component({ + selector: 'app-additional-service', + templateUrl: './additional-service.component.html', + styleUrls: ['./additional-service.component.scss'] +}) +export class AdditionalServiceComponent implements OnInit, OnDestroy { + + @Input() + additionalService: AdditionalService; + + @Input() + form: UntypedFormGroup; + + additionalServiceFormGroup: UntypedFormGroup; + + @Input() + event: Event; + + validSelectionValues: number[] = []; + + private formSub: Subscription; + + constructor(public translate: TranslateService, private formBuilder: UntypedFormBuilder) { } + + public ngOnInit(): void { + + const fa = this.form.get('additionalService') as UntypedFormArray; + + if (this.additionalService.fixPrice) { + this.additionalServiceFormGroup = this.formBuilder.group({additionalServiceId: this.additionalService.id, quantity: null}); + } else { + this.additionalServiceFormGroup = this.formBuilder.group({additionalServiceId: this.additionalService.id, amount: null}); + } + fa.push(this.additionalServiceFormGroup); + + // we only need to recalculate the select box choice in this specific supplement policy! + if (this.additionalService.supplementPolicy === 'OPTIONAL_MAX_AMOUNT_PER_TICKET') { + this.formSub = this.form.get('reservation').valueChanges.subscribe(valueChange => { + const selectedTicketCount = (valueChange as {amount: string}[]).map(a => parseInt(a.amount, 10)).reduce((sum, n) => sum + n, 0); + const rangeEnd = selectedTicketCount * this.additionalService.maxQtyPerOrder; + const res = []; + for (let i = 0; i <= rangeEnd; i++) { + res.push(i); + } + this.validSelectionValues = res; + }); + } else if (this.additionalService.supplementPolicy === 'OPTIONAL_MAX_AMOUNT_PER_RESERVATION' || + this.additionalService.supplementPolicy === null) { + const res = []; + for (let i = 0; i <= this.additionalService.maxQtyPerOrder; i++) { + res.push(i); + } + this.validSelectionValues = res; + } + } + + public ngOnDestroy(): void { + if (this.formSub) { + this.formSub.unsubscribe(); + } + } + +} diff --git a/frontend/projects/public/src/app/app-routing.module.ts b/frontend/projects/public/src/app/app-routing.module.ts new file mode 100644 index 0000000000..3ea4f41c10 --- /dev/null +++ b/frontend/projects/public/src/app/app-routing.module.ts @@ -0,0 +1,77 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {HomeComponent} from './home/home.component'; +import {EventDisplayComponent} from './event-display/event-display.component'; +import {BookingComponent} from './reservation/booking/booking.component'; +import {OverviewComponent} from './reservation/overview/overview.component'; +import {SuccessComponent} from './reservation/success/success.component'; +import {OfflinePaymentComponent} from './reservation/offline-payment/offline-payment.component'; +import {ViewTicketComponent} from './view-ticket/view-ticket.component'; +import {ReservationGuard} from './reservation/reservation.guard'; +import {ProcessingPaymentComponent} from './reservation/processing-payment/processing-payment.component'; +import {LanguageGuard} from './language.guard'; +import {NotFoundComponent} from './reservation/not-found/not-found.component'; +import {EventGuard} from './event.guard'; +import {ErrorComponent} from './reservation/error/error.component'; +import {DeferredOfflinePaymentComponent} from './reservation/deferred-offline-payment/deferred-offline-payment.component'; +import {UpdateTicketComponent} from './update-ticket/update-ticket.component'; +import {EventListAllComponent} from './event-list-all/event-list-all.component'; +import {SubscriptionListAllComponent} from './subscription-list-all/subscription-list-all.component'; +import {RemoveEventCssGuard} from './remove-event-css.guard'; +import {SubscriptionDisplayComponent} from './subscription-display/subscription-display.component'; +import {SuccessSubscriptionComponent} from './reservation/success-subscription/success-subscription.component'; +import {MyOrdersComponent} from './my-orders/my-orders.component'; +import {MyProfileComponent} from './my-profile/my-profile.component'; +import {UserLoggedInGuard} from './user-logged-in.guard'; +import {WaitingRoomComponent} from './waiting-room/waiting-room.component'; + +const eventReservationsGuard = [EventGuard, LanguageGuard, ReservationGuard]; +const eventData = {type: 'event', publicIdentifierParameter: 'eventShortName'}; +const subscriptionReservationsGuard = [LanguageGuard, ReservationGuard]; +const subscriptionData = {type: 'subscription', publicIdentifierParameter: 'id'}; + +const routes: Routes = [ + { path: '', component: HomeComponent, canActivate: [RemoveEventCssGuard, LanguageGuard] }, + { path: 'o/:organizerSlug', component: HomeComponent, canActivate: [RemoveEventCssGuard, LanguageGuard] }, + { path: 'o/:organizerSlug/events-all', component: EventListAllComponent, canActivate: [RemoveEventCssGuard, LanguageGuard] }, + { path: 'events-all', component: EventListAllComponent, canActivate: [RemoveEventCssGuard, LanguageGuard] }, + { path: 'subscriptions-all', component: SubscriptionListAllComponent, canActivate: [RemoveEventCssGuard, LanguageGuard]}, + { path: 'o/:organizerSlug/subscriptions-all', component: SubscriptionListAllComponent, canActivate: [RemoveEventCssGuard, LanguageGuard] }, + { path: 'subscription/:id', component: SubscriptionDisplayComponent, canActivate: [RemoveEventCssGuard, LanguageGuard], data: subscriptionData}, + { path: 'subscription/:id/reservation/:reservationId', data: subscriptionData, children: [ + { path: 'book', component: BookingComponent, canActivate: subscriptionReservationsGuard }, + { path: 'overview', component: OverviewComponent, canActivate: subscriptionReservationsGuard }, + { path: 'waiting-payment', component: OfflinePaymentComponent, canActivate: subscriptionReservationsGuard }, + { path: 'deferred-payment', component: DeferredOfflinePaymentComponent, canActivate: subscriptionReservationsGuard }, + { path: 'processing-payment', component: ProcessingPaymentComponent, canActivate: subscriptionReservationsGuard }, + { path: 'success', component: SuccessSubscriptionComponent, canActivate: subscriptionReservationsGuard }, + { path: 'not-found', component: NotFoundComponent, canActivate: subscriptionReservationsGuard }, + { path: 'error', component: ErrorComponent, canActivate: subscriptionReservationsGuard }, + ]}, + { path: 'event/:eventShortName', component: EventDisplayComponent, canActivate: [EventGuard, LanguageGuard], data: eventData}, + { path: 'event/:eventShortName/poll', loadChildren: () => import('./poll/poll.module').then(m => m.PollModule), canActivate: [EventGuard, LanguageGuard], data: eventData}, + { path: 'event/:eventShortName/reservation/:reservationId', data: eventData, children: [ + { path: 'book', component: BookingComponent, canActivate: eventReservationsGuard }, + { path: 'overview', component: OverviewComponent, canActivate: eventReservationsGuard }, + { path: 'waitingPayment', redirectTo: 'waiting-payment'}, + { path: 'waiting-payment', component: OfflinePaymentComponent, canActivate: eventReservationsGuard }, + { path: 'deferred-payment', component: DeferredOfflinePaymentComponent, canActivate: eventReservationsGuard }, + { path: 'processing-payment', component: ProcessingPaymentComponent, canActivate: eventReservationsGuard }, + { path: 'success', component: SuccessComponent, canActivate: eventReservationsGuard }, + { path: 'not-found', component: NotFoundComponent, canActivate: eventReservationsGuard }, + { path: 'error', component: ErrorComponent, canActivate: eventReservationsGuard }, + ]}, + { path: 'event/:eventShortName/ticket/:ticketId', data: eventData, children: [ + { path: 'view', component: ViewTicketComponent, canActivate: [EventGuard, LanguageGuard] }, + { path: 'update', component: UpdateTicketComponent, canActivate: [EventGuard, LanguageGuard] }, + { path: 'check-in/:ticketCodeHash/waiting-room', component: WaitingRoomComponent, canActivate: [EventGuard, LanguageGuard] } + ]}, + { path: 'my-orders', component: MyOrdersComponent, canActivate: [UserLoggedInGuard, RemoveEventCssGuard, LanguageGuard] }, + { path: 'my-profile', component: MyProfileComponent, canActivate: [UserLoggedInGuard, RemoveEventCssGuard, LanguageGuard] } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/frontend/projects/public/src/app/app.component.html b/frontend/projects/public/src/app/app.component.html new file mode 100644 index 0000000000..4d70c44d66 --- /dev/null +++ b/frontend/projects/public/src/app/app.component.html @@ -0,0 +1,3 @@ +<app-banner-check></app-banner-check> +<router-outlet></router-outlet> +<app-feedback aria-live="polite" aria-atomic="true"></app-feedback> diff --git a/frontend/projects/public/src/app/app.component.ts b/frontend/projects/public/src/app/app.component.ts new file mode 100644 index 0000000000..0cab2c3af2 --- /dev/null +++ b/frontend/projects/public/src/app/app.component.ts @@ -0,0 +1,29 @@ +import {Component, OnDestroy} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; +import {Subscription} from 'rxjs'; +import {I18nService} from './shared/i18n.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html' +}) +export class AppComponent implements OnDestroy { + + private readonly langChangeSub: Subscription; + + constructor(translate: TranslateService, i18nService: I18nService) { + const defaultLang = document.getElementsByTagName('html')[0].getAttribute('lang'); + translate.setDefaultLang(defaultLang); + + this.langChangeSub = translate.onLangChange.subscribe(langChange => { + document.getElementsByTagName('html')[0].setAttribute('lang', langChange.lang); + i18nService.persistLanguage(langChange.lang); + }); + } + + public ngOnDestroy(): void { + if (this.langChangeSub) { + this.langChangeSub.unsubscribe(); + } + } +} diff --git a/frontend/projects/public/src/app/app.module.ts b/frontend/projects/public/src/app/app.module.ts new file mode 100644 index 0000000000..9b072c6fa7 --- /dev/null +++ b/frontend/projects/public/src/app/app.module.ts @@ -0,0 +1,228 @@ +import {BrowserModule} from '@angular/platform-browser'; +import {AlfioCommonModule} from 'common'; +import {APP_INITIALIZER, NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; + +import {AppRoutingModule} from './app-routing.module'; +import {AppComponent} from './app.component'; +import {HomeComponent} from './home/home.component'; +import {EventDisplayComponent} from './event-display/event-display.component'; +import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpClientXsrfModule, HttpXsrfTokenExtractor} from '@angular/common/http'; + +import {FaIconLibrary, FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import { + faAddressCard, + faAngleDown, + faAngleUp, + faCheck, + faCircle, + faCog, + faCreditCard, + faDownload, + faEraser, + faExchangeAlt, + faExclamationCircle, + faExclamationTriangle, + faExternalLinkAlt, + faFileAlt, + faFileInvoice, + faGift, + faGlobe, + faInfoCircle, + faMapMarkerAlt, + faMoneyBill, + faMoneyCheckAlt, + faRedoAlt, + faSearchPlus, + faSignInAlt, + faSignOutAlt, + faThumbsUp, + faTicketAlt, + faTimes, + faTrash, + faUserAstronaut, + faWifi, + faFilePdf +} from '@fortawesome/free-solid-svg-icons'; +import {faBuilding, faCalendarAlt, faCalendarPlus, faCheckCircle, faClock, faClone, faCompass, faCopy, faEdit, faEnvelope, faHandshake} from '@fortawesome/free-regular-svg-icons'; +import {faApplePay, faIdeal, faPaypal, faStripe} from '@fortawesome/free-brands-svg-icons'; +import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; +import {NgbDropdownModule, NgbModalModule, NgbTooltipModule} from '@ng-bootstrap/ng-bootstrap'; +import {NgSelectModule} from '@ng-select/ng-select'; + +import {BookingComponent} from './reservation/booking/booking.component'; +import {OverviewComponent} from './reservation/overview/overview.component'; +import {SuccessComponent} from './reservation/success/success.component'; +import {ReservationComponent} from './reservation/reservation.component'; +import {StepperComponent} from './stepper/stepper.component'; +import {AdditionalFieldComponent} from './additional-field/additional-field.component'; +import {ViewTicketComponent} from './view-ticket/view-ticket.component'; +import {UpdateTicketComponent} from './update-ticket/update-ticket.component'; +import {EventSummaryComponent} from './event-summary/event-summary.component'; +import {TicketFormComponent} from './reservation/ticket-form/ticket-form.component'; +import {CountdownComponent} from './countdown/countdown.component'; +import {BannerCheckComponent} from './banner-check/banner-check.component'; +import {OfflinePaymentComponent} from './reservation/offline-payment/offline-payment.component'; +import {OfflinePaymentProxyComponent} from './payment/offline-payment-proxy/offline-payment-proxy.component'; +import {OnsitePaymentProxyComponent} from './payment/onsite-payment-proxy/onsite-payment-proxy.component'; +import {PaypalPaymentProxyComponent} from './payment/paypal-payment-proxy/paypal-payment-proxy.component'; +import {StripePaymentProxyComponent} from './payment/stripe-payment-proxy/stripe-payment-proxy.component'; +import {SaferpayPaymentProxyComponent} from './payment/saferpay-payment-proxy/saferpay-payment-proxy.component'; +import {ProcessingPaymentComponent} from './reservation/processing-payment/processing-payment.component'; +import {SummaryTableComponent} from './reservation/summary-table/summary-table.component'; +import {InvoiceFormComponent} from './reservation/invoice-form/invoice-form.component'; +import {AdditionalServiceComponent} from './additional-service/additional-service.component'; +import {RecaptchaComponent} from './recaptcha/recaptcha.component'; +import {CustomLoader} from './shared/i18n.service'; +import {NotFoundComponent} from './reservation/not-found/not-found.component'; +import {PriceTagComponent} from './price-tag/price-tag.component'; +import {TicketQuantitySelectorComponent} from './ticket-quantity-selector/ticket-quantity-selector.component'; +import {ItemSalePeriodComponent} from './category-sale-period/item-sale-period.component'; +import {ItemCardComponent} from './item-card/item-card.component'; +import {AdditionalServiceQuantitySelectorComponent} from './additional-service-quantity-selector/additional-service-quantity-selector.component'; +import {ReservationExpiredComponent} from './reservation/expired-notification/reservation-expired.component'; +import {ReleaseTicketComponent} from './reservation/release-ticket/release-ticket.component'; +import {CancelReservationComponent} from './reservation/cancel-reservation/cancel-reservation.component'; +import {FooterLinksComponent} from './event-footer-links/footer-links.component'; +import {ErrorComponent} from './reservation/error/error.component'; +import {DeferredOfflinePaymentComponent} from './reservation/deferred-offline-payment/deferred-offline-payment.component'; +import {MolliePaymentProxyComponent} from './payment/mollie-payment-proxy/mollie-payment-proxy.component'; +import {PaymentMethodSelectorComponent} from './reservation/payment-method-selector/payment-method-selector.component'; +import {AnimatedDotsComponent} from './reservation/animated-dots/animated-dots.component'; +import {EventDatesComponent} from './event-dates/event-dates.component'; +import {SharedModule} from './shared/shared.module'; +import {EventListAllComponent} from './event-list-all/event-list-all.component'; +import {BasicEventInfoComponent} from './basic-event-info/basic-event-info.component'; +import {SubscriptionListAllComponent} from './subscription-list-all/subscription-list-all.component'; +import {SubscriptionDisplayComponent} from './subscription-display/subscription-display.component'; +import {SuccessSubscriptionComponent} from './reservation/success-subscription/success-subscription.component'; +import {BasicSubscriptionInfoComponent} from './basic-subscription-info/basic-subscription-info.component'; +import {SubscriptionSummaryComponent} from './subscription-summary/subscription-summary.component'; +import {PurchaseContextContainerComponent} from './purchase-context-container/purchase-context-container.component'; +import {ModalRemoveSubscriptionComponent} from './reservation/modal-remove-subscription/modal-remove-subscription.component'; +import {UserService} from './shared/user.service'; +import {MyOrdersComponent} from './my-orders/my-orders.component'; +import {MyProfileComponent} from './my-profile/my-profile.component'; +import {WaitingRoomComponent} from './waiting-room/waiting-room.component'; +import {MyProfileDeleteWarningComponent} from './my-profile/my-profile-delete-warning.component'; +import {TranslateDescriptionPipe} from './shared/translate-description.pipe'; +// import { AuthTokenInterceptor, DOMGidExtractor, DOMXsrfTokenExtractor } from 'common'; // FIXME: check why importing from common fail +import { AuthTokenInterceptor, DOMGidExtractor, DOMXsrfTokenExtractor } from './xsrf'; +import {DownloadTicketComponent} from './reservation/download-ticket/download-ticket.component'; + + + +// AoT requires an exported function for factories +export function HttpLoaderFactory(http: HttpClient) { + return new CustomLoader(http); +} + +export function InitUserService(userService: UserService): () => Promise<boolean> { + return () => userService.initAuthenticationStatus(); +} + +@NgModule({ + declarations: [ + AppComponent, + HomeComponent, + EventDisplayComponent, + BookingComponent, + OverviewComponent, + SuccessComponent, + ReservationComponent, + StepperComponent, + AdditionalFieldComponent, + ViewTicketComponent, + UpdateTicketComponent, + EventSummaryComponent, + TicketFormComponent, + DownloadTicketComponent, + CountdownComponent, + BannerCheckComponent, + OfflinePaymentComponent, + OfflinePaymentProxyComponent, + OnsitePaymentProxyComponent, + PaypalPaymentProxyComponent, + StripePaymentProxyComponent, + SaferpayPaymentProxyComponent, + ProcessingPaymentComponent, + SummaryTableComponent, + InvoiceFormComponent, + AdditionalServiceComponent, + RecaptchaComponent, + PriceTagComponent, + NotFoundComponent, + TicketQuantitySelectorComponent, + ItemSalePeriodComponent, + ItemCardComponent, + AdditionalServiceQuantitySelectorComponent, + ReservationExpiredComponent, + ReleaseTicketComponent, + CancelReservationComponent, + FooterLinksComponent, + ErrorComponent, + DeferredOfflinePaymentComponent, + MolliePaymentProxyComponent, + PaymentMethodSelectorComponent, + AnimatedDotsComponent, + EventDatesComponent, + EventListAllComponent, + BasicEventInfoComponent, + BasicSubscriptionInfoComponent, + SubscriptionListAllComponent, + SubscriptionDisplayComponent, + SuccessSubscriptionComponent, + SubscriptionSummaryComponent, + PurchaseContextContainerComponent, + ModalRemoveSubscriptionComponent, + MyOrdersComponent, + MyProfileComponent, + WaitingRoomComponent, + MyProfileDeleteWarningComponent, + TranslateDescriptionPipe + ], + imports: [ + BrowserModule, + AppRoutingModule, + HttpClientModule, + HttpClientXsrfModule.withOptions({ + cookieName: 'XSRF-TOKEN', + headerName: 'X-CSRF-TOKEN', + }), + FontAwesomeModule, + FormsModule, + ReactiveFormsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + NgbTooltipModule, + NgSelectModule, + NgbModalModule, + NgbDropdownModule, + SharedModule, + AlfioCommonModule, + ], + providers: [ + { provide: APP_INITIALIZER, useFactory: InitUserService, deps: [UserService], multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: AuthTokenInterceptor, multi: true }, + { provide: HttpXsrfTokenExtractor, useClass: DOMXsrfTokenExtractor }, + DOMGidExtractor, + DOMXsrfTokenExtractor, + AuthTokenInterceptor + ], + bootstrap: [AppComponent] +}) +export class AppModule { + constructor(library: FaIconLibrary) { + library.addIcons(faInfoCircle, faGift, faTicketAlt, faCheck, faAddressCard, faFileAlt, faThumbsUp, faMoneyBill, + faDownload, faSearchPlus, faExchangeAlt, faExclamationTriangle, faCreditCard, faCog, faEraser, faTimes, faFileInvoice, faGlobe, + faAngleDown, faAngleUp, faCircle, faCheckCircle, faMoneyCheckAlt, faWifi, faTrash, faCopy, faExclamationCircle, faUserAstronaut, + faSignInAlt, faSignOutAlt, faExternalLinkAlt, faMapMarkerAlt, faRedoAlt); + library.addIcons(faCalendarAlt, faCalendarPlus, faCompass, faClock, faEnvelope, faEdit, faClone, faHandshake, faBuilding); + library.addIcons(faPaypal, faStripe, faIdeal, faApplePay, faFilePdf); + } +} diff --git a/frontend/projects/public/src/app/banner-check/banner-check.component.html b/frontend/projects/public/src/app/banner-check/banner-check.component.html new file mode 100644 index 0000000000..409633e289 --- /dev/null +++ b/frontend/projects/public/src/app/banner-check/banner-check.component.html @@ -0,0 +1,13 @@ +<ng-container *ngIf="info"> + <div class="banner alert alert-info alert-dismissible" *ngIf="info.devModeEnabled && !hideAlertInfo"> + You are running alf.io in development mode + <button type="button" class="btn-close" (click)="hideAlertInfo = true"><span class="sr-only">Dismiss</span></button> + </div> + <div class="banner alert alert-danger" *ngIf="info.prodModeEnabled && !secure"> + You are running alf.io in production mode not over https. This is not a recommended configuration. + </div> + <div class="banner alert alert-light alert-dismissible" *ngIf="!hideAnnouncementBanner && info.announcementBannerContentHTML"> + <div [innerHTML]="info.announcementBannerContentHTML"></div> + <button type="button" class="btn-close" (click)="dismissAnnouncementBanner()"><span class="sr-only">Dismiss</span></button> + </div> +</ng-container> diff --git a/frontend/projects/public/src/app/banner-check/banner-check.component.scss b/frontend/projects/public/src/app/banner-check/banner-check.component.scss new file mode 100644 index 0000000000..c8b1bf7098 --- /dev/null +++ b/frontend/projects/public/src/app/banner-check/banner-check.component.scss @@ -0,0 +1,11 @@ +.banner { + position: sticky; + top: 0; + width: 100%; + margin: 0; + border-bottom: none; + border-left: none; + border-right: none; + border-radius: 0; + z-index: 100; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/banner-check/banner-check.component.ts b/frontend/projects/public/src/app/banner-check/banner-check.component.ts new file mode 100644 index 0000000000..20ee1650e7 --- /dev/null +++ b/frontend/projects/public/src/app/banner-check/banner-check.component.ts @@ -0,0 +1,35 @@ +import {Component, OnInit} from '@angular/core'; +import {InfoService} from '../shared/info.service'; +import {Info} from '../model/info'; +import {getFromSessionStorage, writeToSessionStorage} from '../shared/util'; + +const hideAnnouncementBannerKey = 'alfio.hideAnnouncementBanner'; + +@Component({ + selector: 'app-banner-check', + templateUrl: './banner-check.component.html', + styleUrls: ['./banner-check.component.scss'] +}) +export class BannerCheckComponent implements OnInit { + + info: Info; + secure: boolean; + hideAlertInfo: boolean; + hideAnnouncementBanner: boolean; + + constructor(private infoService: InfoService) { } + + ngOnInit() { + this.hideAnnouncementBanner = !!getFromSessionStorage(hideAnnouncementBannerKey); + this.infoService.getInfo().subscribe(info => { + this.info = info; + this.secure = location.protocol.indexOf('https:') === 0; + }); + } + + dismissAnnouncementBanner(): void { + this.hideAnnouncementBanner = true; + writeToSessionStorage(hideAnnouncementBannerKey, 'true'); + } + +} diff --git a/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.html b/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.html new file mode 100644 index 0000000000..d4b6cc87d4 --- /dev/null +++ b/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.html @@ -0,0 +1,18 @@ +<div class="card card-light home-card mt-3"> + <div class="card-header bg-white event-header"> + <img [src]="'/file/'+event.fileBlobId" class="mx-auto img-responsive" alt="" aria-hidden="true" role="presentation"> + <div class="h4 online-flag" *ngIf="isEventOnline"><span class="badge badge-secondary">{{'event.online.badge' | translate}}</span></div> + </div> + <div class="card-body"> + <h2 class="card-title">{{title}}</h2> + <app-event-dates [dateValidityProvider]="event" [displayIcon]="true"></app-event-dates> + <div class="text-muted mt-1" *ngIf="!isEventOnline"> + <fa-icon [icon]="['fas', 'map-marker-alt']"></fa-icon> {{event.location}} + </div> + </div> + <div class="card-footer border-top-0 bg-white"> + <div class="d-flex justify-content-end"> + <a [routerLink]="['/event', event.shortName]" [queryParams]="params" class="btn btn-success stretched-link">{{'event-list.link' | translate}}<span class="sr-only"> {{title}}</span></a> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.scss b/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.scss new file mode 100644 index 0000000000..713c4cb752 --- /dev/null +++ b/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.scss @@ -0,0 +1,25 @@ +.event-header { + height: 100px; + overflow: hidden; + background-position: top center; + background-repeat: no-repeat; + display: flex; + flex-direction: row; + align-items: center; +} + +.event-header > img { + position: relative; + max-height: 80px; + width: auto; +} + +a.no-decoration:hover { + text-decoration:none; +} + +.online-flag { + position: absolute; + top: 10px; + right: 10px; +} diff --git a/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.ts b/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.ts new file mode 100644 index 0000000000..74339d0ead --- /dev/null +++ b/frontend/projects/public/src/app/basic-event-info/basic-event-info.component.ts @@ -0,0 +1,34 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {BasicEventInfo} from '../model/basic-event-info'; +import {TranslateService} from '@ngx-translate/core'; +import {Params} from '@angular/router'; +import {getLocalizedContent} from '../shared/subscription.service'; + +@Component({ + selector: 'app-basic-event-info', + templateUrl: './basic-event-info.component.html', + styleUrls: ['./basic-event-info.component.scss'] +}) +export class BasicEventInfoComponent implements OnInit { + + @Input() + event: BasicEventInfo; + + @Input() + params: Params; + + constructor(private translateService: TranslateService) { } + + ngOnInit(): void { + } + + + public get isEventOnline(): boolean { + return this.event.format === 'ONLINE'; + } + + get title(): string { + return getLocalizedContent(this.event.title, this.translateService.currentLang); + } + +} diff --git a/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.html b/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.html new file mode 100644 index 0000000000..6dbd3012f8 --- /dev/null +++ b/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.html @@ -0,0 +1,16 @@ +<div class="mt-3" [ngClass]="cardLayout ? 'card card-light home-card' : ''"> + <div class="card-header bg-white event-header" *ngIf="cardLayout"> + <img [src]="'/file/'+subscription.fileBlobId" class="mx-auto img-responsive" alt="" aria-hidden="true" role="presentation"> + </div> + <div [class.card-body]="cardLayout"> + <h2 [class.card-title]="cardLayout">{{title}}</h2> + <div class="mt-3"> + <app-subscription-summary [subscription]="subscription" [owner]="owner"></app-subscription-summary> + </div> + </div> + <div class="card-footer border-top-0 bg-white" *ngIf="cardLayout"> + <div class="d-flex justify-content-end"> + <a [routerLink]="['/subscription', subscription.id]" [queryParams]="params" class="btn btn-success stretched-link">{{'subscription-list.link' | translate}}<span class="sr-only"> {{title}}</span></a> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.scss b/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.scss new file mode 100644 index 0000000000..713c4cb752 --- /dev/null +++ b/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.scss @@ -0,0 +1,25 @@ +.event-header { + height: 100px; + overflow: hidden; + background-position: top center; + background-repeat: no-repeat; + display: flex; + flex-direction: row; + align-items: center; +} + +.event-header > img { + position: relative; + max-height: 80px; + width: auto; +} + +a.no-decoration:hover { + text-decoration:none; +} + +.online-flag { + position: absolute; + top: 10px; + right: 10px; +} diff --git a/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.ts b/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.ts new file mode 100644 index 0000000000..c0670c4a4f --- /dev/null +++ b/frontend/projects/public/src/app/basic-subscription-info/basic-subscription-info.component.ts @@ -0,0 +1,33 @@ +import {Component, Input} from '@angular/core'; +import {BasicSubscriptionInfo} from '../model/subscription'; +import {TranslateService} from '@ngx-translate/core'; +import {getLocalizedContent} from '../shared/subscription.service'; +import {SubscriptionOwner} from '../model/reservation-info'; +import {Params} from '@angular/router'; + +@Component({ + selector: 'app-basic-subscription-info', + templateUrl: './basic-subscription-info.component.html', + styleUrls: ['./basic-subscription-info.component.scss'] +}) +export class BasicSubscriptionInfoComponent { + @Input() + subscription: BasicSubscriptionInfo; + @Input() + owner?: SubscriptionOwner; + @Input() + params: Params; + + @Input() + cardLayout = true; + + constructor(private translateService: TranslateService) {} + + get title(): string { + return getLocalizedContent(this.subscription.title, this.translateService.currentLang); + } + + get description(): string { + return getLocalizedContent(this.subscription.description, this.translateService.currentLang); + } +} diff --git a/frontend/projects/public/src/app/category-sale-period/item-sale-period.component.ts b/frontend/projects/public/src/app/category-sale-period/item-sale-period.component.ts new file mode 100644 index 0000000000..290600986c --- /dev/null +++ b/frontend/projects/public/src/app/category-sale-period/item-sale-period.component.ts @@ -0,0 +1,14 @@ +import {Component, Input} from '@angular/core'; +import {TicketCategory} from '../model/ticket-category'; +import {AdditionalService} from '../model/additional-service'; + +@Component({ + selector: 'app-item-sale-period', + templateUrl: './item-sale-period.html' +}) +export class ItemSalePeriodComponent { + @Input() + item: TicketCategory | AdditionalService; + @Input() + currentLang: string; +} diff --git a/frontend/projects/public/src/app/category-sale-period/item-sale-period.html b/frontend/projects/public/src/app/category-sale-period/item-sale-period.html new file mode 100644 index 0000000000..4596b3adb2 --- /dev/null +++ b/frontend/projects/public/src/app/category-sale-period/item-sale-period.html @@ -0,0 +1,5 @@ +<!-- ticket category date handling --> +<div class="h6 card-subtitle text-muted" *ngIf="!item.expired && item.saleInFuture">{{'show-event.sales-not-started'|translate: {'0': item.formattedInception[currentLang]} }}</div> +<div class="h6 card-subtitle text-muted" *ngIf="!item.expired && !item.saleInFuture">{{'show-event.sales-end'|translate: {'0': item.formattedExpiration[currentLang]} }}</div> +<div class="h6 card-subtitle text-muted" *ngIf="item.expired">{{'show-event.sales-ended'|translate: {'0': item.formattedExpiration[currentLang]} }}</div> +<!-- --> diff --git a/frontend/projects/public/src/app/countdown/countdown.component.html b/frontend/projects/public/src/app/countdown/countdown.component.html new file mode 100644 index 0000000000..c88bc76a69 --- /dev/null +++ b/frontend/projects/public/src/app/countdown/countdown.component.html @@ -0,0 +1,5 @@ +<div class="alert text-center" [ngClass]="'alert-'+alertType" [class.sticky]="displaySticky"> + <fa-icon [icon]="['far', 'clock']" a11yRole="presentation"></fa-icon>{{' '}} + <span *ngIf="!isExpired" [innerHTML]="message"></span> + <strong *ngIf="isExpired" translate="error.STEP_2_ORDER_HAS_EXPIRED"></strong> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/countdown/countdown.component.ts b/frontend/projects/public/src/app/countdown/countdown.component.ts new file mode 100644 index 0000000000..b1ad7dbed5 --- /dev/null +++ b/frontend/projects/public/src/app/countdown/countdown.component.ts @@ -0,0 +1,76 @@ +import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; +import * as countdown from './countdown'; // see issue: https://stackoverflow.com/a/70506557 + https://github.com/mckamey/countdownjs/issues/39 +import {Subscription} from 'rxjs'; + +@Component({ + selector: 'app-countdown', + templateUrl: './countdown.component.html', + styleUrls: ['./countdown.scss'] +}) +export class CountdownComponent implements OnInit, OnDestroy { + + @Input() + validity: number; + + @Output() + expired: EventEmitter<boolean> = new EventEmitter<boolean>(); + + timerId: number; + + message: string; + isExpired: boolean; + alertType = 'info'; + displaySticky = false; + + private langChangeSub: Subscription; + + constructor(private translateService: TranslateService) { } + + ngOnInit() { + this.setupCountdown(); + + this.langChangeSub = this.translateService.onLangChange.subscribe(e => { + this.setupCountdown(); + }); + } + + ngOnDestroy() { + if (this.langChangeSub) { + this.langChangeSub.unsubscribe(); + } + clearInterval(this.timerId); + } + + private setupCountdown(): void { + + clearInterval(this.timerId); + + const msg = this.translateService.instant('reservation-page.time-for-completion'); + const singular = this.translateService.instant('reservation-page.time-for-completion.labels.singular'); + const plural = this.translateService.instant('reservation-page.time-for-completion.labels.plural'); + const and = this.translateService.instant('reservation-page.time-for-completion.labels.and'); + const oneSecond = 1000; + const oneMinute = 60 * oneSecond; + const fiveMinutes = 5 * oneMinute; + + countdown.setLabels(singular, plural, ' ' + and + ' ', ', '); + this.timerId = countdown(new Date(this.validity), (ts) => { + const absDifference = Math.abs(ts.value); + if (ts.value < 0 && absDifference >= oneSecond) { + if (absDifference < fiveMinutes) { + this.alertType = absDifference < oneMinute ? 'danger' : 'warning'; + this.displaySticky = true; + } + this.message = msg.replace('##time##', ts.toHTML('strong')); + } else { + this.isExpired = true; + this.alertType = 'danger'; + clearInterval(this.timerId); + this.expired.emit(true); + } + // tslint:disable-next-line: no-bitwise + }, countdown.MONTHS | countdown.WEEKS | countdown.DAYS | countdown.HOURS | countdown.MINUTES | countdown.SECONDS) as number; + } + +} diff --git a/frontend/projects/public/src/app/countdown/countdown.js b/frontend/projects/public/src/app/countdown/countdown.js new file mode 100644 index 0000000000..7920bb3ce7 --- /dev/null +++ b/frontend/projects/public/src/app/countdown/countdown.js @@ -0,0 +1,1357 @@ +/*global window module */ +/** + * @license countdown.js v2.6.1 http://countdownjs.org + * Copyright (c)2006-2014 Stephen M. McKamey. + * Licensed under The MIT License. + */ +/*jshint bitwise:false */ + +/** + * API entry + * @public + * @param {function(Object)|Date|number} start the starting date + * @param {function(Object)|Date|number} end the ending date + * @param {number} units the units to populate + * @return {Object|number} + */ + var countdown = ( + + function() { + /*jshint smarttabs:true */ + + 'use strict'; + + /** + * @private + * @const + * @type {number} + */ + var MILLISECONDS = 0x001; + + /** + * @private + * @const + * @type {number} + */ + var SECONDS = 0x002; + + /** + * @private + * @const + * @type {number} + */ + var MINUTES = 0x004; + + /** + * @private + * @const + * @type {number} + */ + var HOURS = 0x008; + + /** + * @private + * @const + * @type {number} + */ + var DAYS = 0x010; + + /** + * @private + * @const + * @type {number} + */ + var WEEKS = 0x020; + + /** + * @private + * @const + * @type {number} + */ + var MONTHS = 0x040; + + /** + * @private + * @const + * @type {number} + */ + var YEARS = 0x080; + + /** + * @private + * @const + * @type {number} + */ + var DECADES = 0x100; + + /** + * @private + * @const + * @type {number} + */ + var CENTURIES = 0x200; + + /** + * @private + * @const + * @type {number} + */ + var MILLENNIA = 0x400; + + /** + * @private + * @const + * @type {number} + */ + var DEFAULTS = YEARS|MONTHS|DAYS|HOURS|MINUTES|SECONDS; + + /** + * @private + * @const + * @type {number} + */ + var MILLISECONDS_PER_SECOND = 1000; + + /** + * @private + * @const + * @type {number} + */ + var SECONDS_PER_MINUTE = 60; + + /** + * @private + * @const + * @type {number} + */ + var MINUTES_PER_HOUR = 60; + + /** + * @private + * @const + * @type {number} + */ + var HOURS_PER_DAY = 24; + + /** + * @private + * @const + * @type {number} + */ + var MILLISECONDS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; + + /** + * @private + * @const + * @type {number} + */ + var DAYS_PER_WEEK = 7; + + /** + * @private + * @const + * @type {number} + */ + var MONTHS_PER_YEAR = 12; + + /** + * @private + * @const + * @type {number} + */ + var YEARS_PER_DECADE = 10; + + /** + * @private + * @const + * @type {number} + */ + var DECADES_PER_CENTURY = 10; + + /** + * @private + * @const + * @type {number} + */ + var CENTURIES_PER_MILLENNIUM = 10; + + /** + * @private + * @param {number} x number + * @return {number} + */ + var ceil = Math.ceil; + + /** + * @private + * @param {number} x number + * @return {number} + */ + var floor = Math.floor; + + /** + * @private + * @param {Date} ref reference date + * @param {number} shift number of months to shift + * @return {number} number of days shifted + */ + function borrowMonths(ref, shift) { + var prevTime = ref.getTime(); + + // increment month by shift + ref.setMonth( ref.getMonth() + shift ); + + // this is the trickiest since months vary in length + return Math.round( (ref.getTime() - prevTime) / MILLISECONDS_PER_DAY ); + } + + /** + * @private + * @param {Date} ref reference date + * @return {number} number of days + */ + function daysPerMonth(ref) { + var a = ref.getTime(); + + // increment month by 1 + var b = new Date(a); + b.setMonth( ref.getMonth() + 1 ); + + // this is the trickiest since months vary in length + return Math.round( (b.getTime() - a) / MILLISECONDS_PER_DAY ); + } + + /** + * @private + * @param {Date} ref reference date + * @return {number} number of days + */ + function daysPerYear(ref) { + var a = ref.getTime(); + + // increment year by 1 + var b = new Date(a); + b.setFullYear( ref.getFullYear() + 1 ); + + // this is the trickiest since years (periodically) vary in length + return Math.round( (b.getTime() - a) / MILLISECONDS_PER_DAY ); + } + + /** + * Applies the Timespan to the given date. + * + * @private + * @param {Timespan} ts + * @param {Date=} date + * @return {Date} + */ + function addToDate(ts, date) { + date = (date instanceof Date) || ((date !== null) && isFinite(date)) ? new Date(+date) : new Date(); + if (!ts) { + return date; + } + + // if there is a value field, use it directly + var value = +ts.value || 0; + if (value) { + date.setTime(date.getTime() + value); + return date; + } + + value = +ts.milliseconds || 0; + if (value) { + date.setMilliseconds(date.getMilliseconds() + value); + } + + value = +ts.seconds || 0; + if (value) { + date.setSeconds(date.getSeconds() + value); + } + + value = +ts.minutes || 0; + if (value) { + date.setMinutes(date.getMinutes() + value); + } + + value = +ts.hours || 0; + if (value) { + date.setHours(date.getHours() + value); + } + + value = +ts.weeks || 0; + if (value) { + value *= DAYS_PER_WEEK; + } + + value += +ts.days || 0; + if (value) { + date.setDate(date.getDate() + value); + } + + value = +ts.months || 0; + if (value) { + date.setMonth(date.getMonth() + value); + } + + value = +ts.millennia || 0; + if (value) { + value *= CENTURIES_PER_MILLENNIUM; + } + + value += +ts.centuries || 0; + if (value) { + value *= DECADES_PER_CENTURY; + } + + value += +ts.decades || 0; + if (value) { + value *= YEARS_PER_DECADE; + } + + value += +ts.years || 0; + if (value) { + date.setFullYear(date.getFullYear() + value); + } + + return date; + } + + /** + * @private + * @const + * @type {number} + */ + var LABEL_MILLISECONDS = 0; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_SECONDS = 1; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_MINUTES = 2; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_HOURS = 3; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_DAYS = 4; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_WEEKS = 5; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_MONTHS = 6; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_YEARS = 7; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_DECADES = 8; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_CENTURIES = 9; + + /** + * @private + * @const + * @type {number} + */ + var LABEL_MILLENNIA = 10; + + /** + * @private + * @type {Array} + */ + var LABELS_SINGLUAR; + + /** + * @private + * @type {Array} + */ + var LABELS_PLURAL; + + /** + * @private + * @type {string} + */ + var LABEL_LAST; + + /** + * @private + * @type {string} + */ + var LABEL_DELIM; + + /** + * @private + * @type {string} + */ + var LABEL_NOW; + + /** + * Formats a number & unit as a string + * + * @param {number} value + * @param {number} unit + * @return {string} + */ + var formatter; + + /** + * Formats a number as a string + * + * @private + * @param {number} value + * @return {string} + */ + var formatNumber; + + /** + * @private + * @param {number} value + * @param {number} unit unit index into label list + * @return {string} + */ + function plurality(value, unit) { + return formatNumber(value)+((value === 1) ? LABELS_SINGLUAR[unit] : LABELS_PLURAL[unit]); + } + + /** + * Formats the entries with singular or plural labels + * + * @private + * @param {Timespan} ts + * @return {Array} + */ + var formatList; + + /** + * Timespan representation of a duration of time + * + * @private + * @this {Timespan} + * @constructor + */ + function Timespan() {} + + /** + * Formats the Timespan as a sentence + * + * @param {string=} emptyLabel the string to use when no values returned + * @return {string} + */ + Timespan.prototype.toString = function(emptyLabel) { + var label = formatList(this); + + var count = label.length; + if (!count) { + return emptyLabel ? ''+emptyLabel : LABEL_NOW; + } + if (count === 1) { + return label[0]; + } + + var last = LABEL_LAST+label.pop(); + return label.join(LABEL_DELIM)+last; + }; + + /** + * Formats the Timespan as a sentence in HTML + * + * @param {string=} tag HTML tag name to wrap each value + * @param {string=} emptyLabel the string to use when no values returned + * @return {string} + */ + Timespan.prototype.toHTML = function(tag, emptyLabel) { + tag = tag || 'span'; + var label = formatList(this); + + var count = label.length; + if (!count) { + emptyLabel = emptyLabel || LABEL_NOW; + return emptyLabel ? '<'+tag+'>'+emptyLabel+'</'+tag+'>' : emptyLabel; + } + for (var i=0; i<count; i++) { + // wrap each unit in tag + label[i] = '<'+tag+'>'+label[i]+'</'+tag+'>'; + } + if (count === 1) { + return label[0]; + } + + var last = LABEL_LAST+label.pop(); + return label.join(LABEL_DELIM)+last; + }; + + /** + * Applies the Timespan to the given date + * + * @param {Date=} date the date to which the timespan is added. + * @return {Date} + */ + Timespan.prototype.addTo = function(date) { + return addToDate(this, date); + }; + + /** + * Formats the entries as English labels + * + * @private + * @param {Timespan} ts + * @return {Array} + */ + formatList = function(ts) { + var list = []; + + var value = ts.millennia; + if (value) { + list.push(formatter(value, LABEL_MILLENNIA)); + } + + value = ts.centuries; + if (value) { + list.push(formatter(value, LABEL_CENTURIES)); + } + + value = ts.decades; + if (value) { + list.push(formatter(value, LABEL_DECADES)); + } + + value = ts.years; + if (value) { + list.push(formatter(value, LABEL_YEARS)); + } + + value = ts.months; + if (value) { + list.push(formatter(value, LABEL_MONTHS)); + } + + value = ts.weeks; + if (value) { + list.push(formatter(value, LABEL_WEEKS)); + } + + value = ts.days; + if (value) { + list.push(formatter(value, LABEL_DAYS)); + } + + value = ts.hours; + if (value) { + list.push(formatter(value, LABEL_HOURS)); + } + + value = ts.minutes; + if (value) { + list.push(formatter(value, LABEL_MINUTES)); + } + + value = ts.seconds; + if (value) { + list.push(formatter(value, LABEL_SECONDS)); + } + + value = ts.milliseconds; + if (value) { + list.push(formatter(value, LABEL_MILLISECONDS)); + } + + return list; + }; + + /** + * Borrow any underflow units, carry any overflow units + * + * @private + * @param {Timespan} ts + * @param {string} toUnit + */ + function rippleRounded(ts, toUnit) { + switch (toUnit) { + case 'seconds': + if (ts.seconds !== SECONDS_PER_MINUTE || isNaN(ts.minutes)) { + return; + } + // ripple seconds up to minutes + ts.minutes++; + ts.seconds = 0; + + /* falls through */ + case 'minutes': + if (ts.minutes !== MINUTES_PER_HOUR || isNaN(ts.hours)) { + return; + } + // ripple minutes up to hours + ts.hours++; + ts.minutes = 0; + + /* falls through */ + case 'hours': + if (ts.hours !== HOURS_PER_DAY || isNaN(ts.days)) { + return; + } + // ripple hours up to days + ts.days++; + ts.hours = 0; + + /* falls through */ + case 'days': + if (ts.days !== DAYS_PER_WEEK || isNaN(ts.weeks)) { + return; + } + // ripple days up to weeks + ts.weeks++; + ts.days = 0; + + /* falls through */ + case 'weeks': + if (ts.weeks !== daysPerMonth(ts.refMonth)/DAYS_PER_WEEK || isNaN(ts.months)) { + return; + } + // ripple weeks up to months + ts.months++; + ts.weeks = 0; + + /* falls through */ + case 'months': + if (ts.months !== MONTHS_PER_YEAR || isNaN(ts.years)) { + return; + } + // ripple months up to years + ts.years++; + ts.months = 0; + + /* falls through */ + case 'years': + if (ts.years !== YEARS_PER_DECADE || isNaN(ts.decades)) { + return; + } + // ripple years up to decades + ts.decades++; + ts.years = 0; + + /* falls through */ + case 'decades': + if (ts.decades !== DECADES_PER_CENTURY || isNaN(ts.centuries)) { + return; + } + // ripple decades up to centuries + ts.centuries++; + ts.decades = 0; + + /* falls through */ + case 'centuries': + if (ts.centuries !== CENTURIES_PER_MILLENNIUM || isNaN(ts.millennia)) { + return; + } + // ripple centuries up to millennia + ts.millennia++; + ts.centuries = 0; + /* falls through */ + } + } + + /** + * Ripple up partial units one place + * + * @private + * @param {Timespan} ts timespan + * @param {number} frac accumulated fractional value + * @param {string} fromUnit source unit name + * @param {string} toUnit target unit name + * @param {number} conversion multiplier between units + * @param {number} digits max number of decimal digits to output + * @return {number} new fractional value + */ + function fraction(ts, frac, fromUnit, toUnit, conversion, digits) { + if (ts[fromUnit] >= 0) { + frac += ts[fromUnit]; + delete ts[fromUnit]; + } + + frac /= conversion; + if (frac + 1 <= 1) { + // drop if below machine epsilon + return 0; + } + + if (ts[toUnit] >= 0) { + // ensure does not have more than specified number of digits + ts[toUnit] = +(ts[toUnit] + frac).toFixed(digits); + rippleRounded(ts, toUnit); + return 0; + } + + return frac; + } + + /** + * Ripple up partial units to next existing + * + * @private + * @param {Timespan} ts + * @param {number} digits max number of decimal digits to output + */ + function fractional(ts, digits) { + var frac = fraction(ts, 0, 'milliseconds', 'seconds', MILLISECONDS_PER_SECOND, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'seconds', 'minutes', SECONDS_PER_MINUTE, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'minutes', 'hours', MINUTES_PER_HOUR, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'hours', 'days', HOURS_PER_DAY, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'days', 'weeks', DAYS_PER_WEEK, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'weeks', 'months', daysPerMonth(ts.refMonth)/DAYS_PER_WEEK, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'months', 'years', daysPerYear(ts.refMonth)/daysPerMonth(ts.refMonth), digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'years', 'decades', YEARS_PER_DECADE, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'decades', 'centuries', DECADES_PER_CENTURY, digits); + if (!frac) { return; } + + frac = fraction(ts, frac, 'centuries', 'millennia', CENTURIES_PER_MILLENNIUM, digits); + + // should never reach this with remaining fractional value + if (frac) { throw new Error('Fractional unit overflow'); } + } + + /** + * Borrow any underflow units, carry any overflow units + * + * @private + * @param {Timespan} ts + */ + function ripple(ts) { + var x; + + if (ts.milliseconds < 0) { + // ripple seconds down to milliseconds + x = ceil(-ts.milliseconds / MILLISECONDS_PER_SECOND); + ts.seconds -= x; + ts.milliseconds += x * MILLISECONDS_PER_SECOND; + + } else if (ts.milliseconds >= MILLISECONDS_PER_SECOND) { + // ripple milliseconds up to seconds + ts.seconds += floor(ts.milliseconds / MILLISECONDS_PER_SECOND); + ts.milliseconds %= MILLISECONDS_PER_SECOND; + } + + if (ts.seconds < 0) { + // ripple minutes down to seconds + x = ceil(-ts.seconds / SECONDS_PER_MINUTE); + ts.minutes -= x; + ts.seconds += x * SECONDS_PER_MINUTE; + + } else if (ts.seconds >= SECONDS_PER_MINUTE) { + // ripple seconds up to minutes + ts.minutes += floor(ts.seconds / SECONDS_PER_MINUTE); + ts.seconds %= SECONDS_PER_MINUTE; + } + + if (ts.minutes < 0) { + // ripple hours down to minutes + x = ceil(-ts.minutes / MINUTES_PER_HOUR); + ts.hours -= x; + ts.minutes += x * MINUTES_PER_HOUR; + + } else if (ts.minutes >= MINUTES_PER_HOUR) { + // ripple minutes up to hours + ts.hours += floor(ts.minutes / MINUTES_PER_HOUR); + ts.minutes %= MINUTES_PER_HOUR; + } + + if (ts.hours < 0) { + // ripple days down to hours + x = ceil(-ts.hours / HOURS_PER_DAY); + ts.days -= x; + ts.hours += x * HOURS_PER_DAY; + + } else if (ts.hours >= HOURS_PER_DAY) { + // ripple hours up to days + ts.days += floor(ts.hours / HOURS_PER_DAY); + ts.hours %= HOURS_PER_DAY; + } + + while (ts.days < 0) { + // NOTE: never actually seen this loop more than once + + // ripple months down to days + ts.months--; + ts.days += borrowMonths(ts.refMonth, 1); + } + + // weeks is always zero here + + if (ts.days >= DAYS_PER_WEEK) { + // ripple days up to weeks + ts.weeks += floor(ts.days / DAYS_PER_WEEK); + ts.days %= DAYS_PER_WEEK; + } + + if (ts.months < 0) { + // ripple years down to months + x = ceil(-ts.months / MONTHS_PER_YEAR); + ts.years -= x; + ts.months += x * MONTHS_PER_YEAR; + + } else if (ts.months >= MONTHS_PER_YEAR) { + // ripple months up to years + ts.years += floor(ts.months / MONTHS_PER_YEAR); + ts.months %= MONTHS_PER_YEAR; + } + + // years is always non-negative here + // decades, centuries and millennia are always zero here + + if (ts.years >= YEARS_PER_DECADE) { + // ripple years up to decades + ts.decades += floor(ts.years / YEARS_PER_DECADE); + ts.years %= YEARS_PER_DECADE; + + if (ts.decades >= DECADES_PER_CENTURY) { + // ripple decades up to centuries + ts.centuries += floor(ts.decades / DECADES_PER_CENTURY); + ts.decades %= DECADES_PER_CENTURY; + + if (ts.centuries >= CENTURIES_PER_MILLENNIUM) { + // ripple centuries up to millennia + ts.millennia += floor(ts.centuries / CENTURIES_PER_MILLENNIUM); + ts.centuries %= CENTURIES_PER_MILLENNIUM; + } + } + } + } + + /** + * Remove any units not requested + * + * @private + * @param {Timespan} ts + * @param {number} units the units to populate + * @param {number} max number of labels to output + * @param {number} digits max number of decimal digits to output + */ + function pruneUnits(ts, units, max, digits) { + var count = 0; + + // Calc from largest unit to smallest to prevent underflow + if (!(units & MILLENNIA) || (count >= max)) { + // ripple millennia down to centuries + ts.centuries += ts.millennia * CENTURIES_PER_MILLENNIUM; + delete ts.millennia; + + } else if (ts.millennia) { + count++; + } + + if (!(units & CENTURIES) || (count >= max)) { + // ripple centuries down to decades + ts.decades += ts.centuries * DECADES_PER_CENTURY; + delete ts.centuries; + + } else if (ts.centuries) { + count++; + } + + if (!(units & DECADES) || (count >= max)) { + // ripple decades down to years + ts.years += ts.decades * YEARS_PER_DECADE; + delete ts.decades; + + } else if (ts.decades) { + count++; + } + + if (!(units & YEARS) || (count >= max)) { + // ripple years down to months + ts.months += ts.years * MONTHS_PER_YEAR; + delete ts.years; + + } else if (ts.years) { + count++; + } + + if (!(units & MONTHS) || (count >= max)) { + // ripple months down to days + if (ts.months) { + ts.days += borrowMonths(ts.refMonth, ts.months); + } + delete ts.months; + + if (ts.days >= DAYS_PER_WEEK) { + // ripple day overflow back up to weeks + ts.weeks += floor(ts.days / DAYS_PER_WEEK); + ts.days %= DAYS_PER_WEEK; + } + + } else if (ts.months) { + count++; + } + + if (!(units & WEEKS) || (count >= max)) { + // ripple weeks down to days + ts.days += ts.weeks * DAYS_PER_WEEK; + delete ts.weeks; + + } else if (ts.weeks) { + count++; + } + + if (!(units & DAYS) || (count >= max)) { + //ripple days down to hours + ts.hours += ts.days * HOURS_PER_DAY; + delete ts.days; + + } else if (ts.days) { + count++; + } + + if (!(units & HOURS) || (count >= max)) { + // ripple hours down to minutes + ts.minutes += ts.hours * MINUTES_PER_HOUR; + delete ts.hours; + + } else if (ts.hours) { + count++; + } + + if (!(units & MINUTES) || (count >= max)) { + // ripple minutes down to seconds + ts.seconds += ts.minutes * SECONDS_PER_MINUTE; + delete ts.minutes; + + } else if (ts.minutes) { + count++; + } + + if (!(units & SECONDS) || (count >= max)) { + // ripple seconds down to milliseconds + ts.milliseconds += ts.seconds * MILLISECONDS_PER_SECOND; + delete ts.seconds; + + } else if (ts.seconds) { + count++; + } + + // nothing to ripple milliseconds down to + // so ripple back up to smallest existing unit as a fractional value + if (!(units & MILLISECONDS) || (count >= max)) { + fractional(ts, digits); + } + } + + /** + * Populates the Timespan object + * + * @private + * @param {Timespan} ts + * @param {?Date} start the starting date + * @param {?Date} end the ending date + * @param {number} units the units to populate + * @param {number} max number of labels to output + * @param {number} digits max number of decimal digits to output + */ + function populate(ts, start, end, units, max, digits) { + var now = new Date(); + + ts.start = start = start || now; + ts.end = end = end || now; + ts.units = units; + + ts.value = end.getTime() - start.getTime(); + if (ts.value < 0) { + // swap if reversed + var tmp = end; + end = start; + start = tmp; + } + + // reference month for determining days in month + ts.refMonth = new Date(start.getFullYear(), start.getMonth(), 15, 12, 0, 0); + try { + // reset to initial deltas + ts.millennia = 0; + ts.centuries = 0; + ts.decades = 0; + ts.years = end.getFullYear() - start.getFullYear(); + ts.months = end.getMonth() - start.getMonth(); + ts.weeks = 0; + ts.days = end.getDate() - start.getDate(); + ts.hours = end.getHours() - start.getHours(); + ts.minutes = end.getMinutes() - start.getMinutes(); + ts.seconds = end.getSeconds() - start.getSeconds(); + ts.milliseconds = end.getMilliseconds() - start.getMilliseconds(); + + ripple(ts); + pruneUnits(ts, units, max, digits); + + } finally { + delete ts.refMonth; + } + + return ts; + } + + /** + * Determine an appropriate refresh rate based upon units + * + * @private + * @param {number} units the units to populate + * @return {number} milliseconds to delay + */ + function getDelay(units) { + if (units & MILLISECONDS) { + // refresh very quickly + return MILLISECONDS_PER_SECOND / 30; //30Hz + } + + if (units & SECONDS) { + // refresh every second + return MILLISECONDS_PER_SECOND; //1Hz + } + + if (units & MINUTES) { + // refresh every minute + return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE; + } + + if (units & HOURS) { + // refresh hourly + return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR; + } + + if (units & DAYS) { + // refresh daily + return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY; + } + + // refresh the rest weekly + return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * DAYS_PER_WEEK; + } + + /** + * API entry point + * + * @public + * @param {Date|number|Timespan|null|function(Timespan,number)} start the starting date + * @param {Date|number|Timespan|null|function(Timespan,number)} end the ending date + * @param {number=} units the units to populate + * @param {number=} max number of labels to output + * @param {number=} digits max number of decimal digits to output + * @return {Timespan|number} + */ + function countdown(start, end, units, max, digits) { + var callback; + + // ensure some units or use defaults + units = +units || DEFAULTS; + // max must be positive + max = (max > 0) ? max : NaN; + // clamp digits to an integer between [0, 20] + digits = (digits > 0) ? (digits < 20) ? Math.round(digits) : 20 : 0; + + // ensure start date + var startTS = null; + if ('function' === typeof start) { + callback = start; + start = null; + + } else if (!(start instanceof Date)) { + if ((start !== null) && isFinite(start)) { + start = new Date(+start); + } else { + if ('object' === typeof startTS) { + startTS = /** @type{Timespan} */(start); + } + start = null; + } + } + + // ensure end date + var endTS = null; + if ('function' === typeof end) { + callback = end; + end = null; + + } else if (!(end instanceof Date)) { + if ((end !== null) && isFinite(end)) { + end = new Date(+end); + } else { + if ('object' === typeof end) { + endTS = /** @type{Timespan} */(end); + } + end = null; + } + } + + // must wait to interpret timespans until after resolving dates + if (startTS) { + start = addToDate(startTS, end); + } + if (endTS) { + end = addToDate(endTS, start); + } + + if (!start && !end) { + // used for unit testing + return new Timespan(); + } + + if (!callback) { + return populate(new Timespan(), /** @type{Date} */(start), /** @type{Date} */(end), /** @type{number} */(units), /** @type{number} */(max), /** @type{number} */(digits)); + } + + // base delay off units + var delay = getDelay(units), + timerId, + fn = function() { + callback( + populate(new Timespan(), /** @type{Date} */(start), /** @type{Date} */(end), /** @type{number} */(units), /** @type{number} */(max), /** @type{number} */(digits)), + timerId + ); + }; + + fn(); + return (timerId = setInterval(fn, delay)); + } + + /** + * @public + * @const + * @type {number} + */ + countdown.MILLISECONDS = MILLISECONDS; + + /** + * @public + * @const + * @type {number} + */ + countdown.SECONDS = SECONDS; + + /** + * @public + * @const + * @type {number} + */ + countdown.MINUTES = MINUTES; + + /** + * @public + * @const + * @type {number} + */ + countdown.HOURS = HOURS; + + /** + * @public + * @const + * @type {number} + */ + countdown.DAYS = DAYS; + + /** + * @public + * @const + * @type {number} + */ + countdown.WEEKS = WEEKS; + + /** + * @public + * @const + * @type {number} + */ + countdown.MONTHS = MONTHS; + + /** + * @public + * @const + * @type {number} + */ + countdown.YEARS = YEARS; + + /** + * @public + * @const + * @type {number} + */ + countdown.DECADES = DECADES; + + /** + * @public + * @const + * @type {number} + */ + countdown.CENTURIES = CENTURIES; + + /** + * @public + * @const + * @type {number} + */ + countdown.MILLENNIA = MILLENNIA; + + /** + * @public + * @const + * @type {number} + */ + countdown.DEFAULTS = DEFAULTS; + + /** + * @public + * @const + * @type {number} + */ + countdown.ALL = MILLENNIA|CENTURIES|DECADES|YEARS|MONTHS|WEEKS|DAYS|HOURS|MINUTES|SECONDS|MILLISECONDS; + + /** + * Customize the format settings. + * @public + * @param {Object} format settings object + */ + var setFormat = countdown.setFormat = function(format) { + if (!format) { return; } + + if ('singular' in format || 'plural' in format) { + var singular = format.singular || []; + if (singular.split) { + singular = singular.split('|'); + } + var plural = format.plural || []; + if (plural.split) { + plural = plural.split('|'); + } + + for (var i=LABEL_MILLISECONDS; i<=LABEL_MILLENNIA; i++) { + // override any specified units + LABELS_SINGLUAR[i] = singular[i] || LABELS_SINGLUAR[i]; + LABELS_PLURAL[i] = plural[i] || LABELS_PLURAL[i]; + } + } + + if ('string' === typeof format.last) { + LABEL_LAST = format.last; + } + if ('string' === typeof format.delim) { + LABEL_DELIM = format.delim; + } + if ('string' === typeof format.empty) { + LABEL_NOW = format.empty; + } + if ('function' === typeof format.formatNumber) { + formatNumber = format.formatNumber; + } + if ('function' === typeof format.formatter) { + formatter = format.formatter; + } + }; + + /** + * Revert to the default formatting. + * @public + */ + var resetFormat = countdown.resetFormat = function() { + LABELS_SINGLUAR = ' millisecond| second| minute| hour| day| week| month| year| decade| century| millennium'.split('|'); + LABELS_PLURAL = ' milliseconds| seconds| minutes| hours| days| weeks| months| years| decades| centuries| millennia'.split('|'); + LABEL_LAST = ' and '; + LABEL_DELIM = ', '; + LABEL_NOW = ''; + formatNumber = function(value) { return value; }; + formatter = plurality; + }; + + /** + * Override the unit labels. + * @public + * @param {string|Array=} singular a pipe ('|') delimited list of singular unit name overrides + * @param {string|Array=} plural a pipe ('|') delimited list of plural unit name overrides + * @param {string=} last a delimiter before the last unit (default: ' and ') + * @param {string=} delim a delimiter to use between all other units (default: ', ') + * @param {string=} empty a label to use when all units are zero (default: '') + * @param {function(number):string=} formatNumber a function which formats numbers as a string + * @param {function(number,number):string=} formatter a function which formats a number/unit pair as a string + * @deprecated since version 2.6.0 + */ + countdown.setLabels = function(singular, plural, last, delim, empty, formatNumber, formatter) { + setFormat({ + singular: singular, + plural: plural, + last: last, + delim: delim, + empty: empty, + formatNumber: formatNumber, + formatter: formatter + }); + }; + + /** + * Revert to the default unit labels. + * @public + * @deprecated since version 2.6.0 + */ + countdown.resetLabels = resetFormat; + + resetFormat(); + + if (typeof module !== 'undefined' && module.exports) { + module.exports = countdown; + + } else if (typeof window !== 'undefined' && typeof window.define === 'function' && typeof window.define.amd !== 'undefined') { + window.define('countdown', [], function() { + return countdown; + }); + } + + return countdown; + + })(); \ No newline at end of file diff --git a/frontend/projects/public/src/app/countdown/countdown.scss b/frontend/projects/public/src/app/countdown/countdown.scss new file mode 100644 index 0000000000..e7dfc5dd8e --- /dev/null +++ b/frontend/projects/public/src/app/countdown/countdown.scss @@ -0,0 +1,7 @@ +div.sticky { + position: -webkit-sticky; + position: sticky; + top: 0px; + left: 0px; + z-index: 200; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/event-dates/event-dates.component.html b/frontend/projects/public/src/app/event-dates/event-dates.component.html new file mode 100644 index 0000000000..37c86777ff --- /dev/null +++ b/frontend/projects/public/src/app/event-dates/event-dates.component.html @@ -0,0 +1,24 @@ +<!-- same day--> +<span *ngIf="dateValidityProvider.sameDay"> + <span *ngIf="displayIcon"><fa-icon [icon]="['far', 'calendar-alt']" class="text-muted" [title]="'ticket.date-time' | translate"></fa-icon> </span> + {{' ' + dateValidityProvider.formattedBeginDate[translate.currentLang]}} {{'event-days.single-day.hours'|translate: {'0': dateValidityProvider.formattedBeginTime[translate.currentLang], '1': dateValidityProvider.formattedEndTime[translate.currentLang]} }} + <span class="text-muted" *ngIf="displayTimeZoneInfo"> ({{dateValidityProvider.timeZone}})</span> +</span> +<!-- more than one day--> +<div *ngIf="!dateValidityProvider.sameDay"> + <div class="row"> + <div class="col-1" *ngIf="displayIcon"><fa-icon [icon]="['far', 'calendar-alt']" class="text-muted" [title]="'ticket.date-time' | translate"></fa-icon></div> + <div [ngClass]="{'col-11': displayIcon, 'col-12': !displayIcon}"> + <div> + <span class="me-2"><strong translate="event-days.from"></strong></span> + <span>{{localizedStartDateForMultiDay}}</span> + <span class="text-muted" *ngIf="displayTimeZoneInfo"> ({{dateValidityProvider.timeZone}})</span> + </div> + <div> + <span class="me-2"><strong translate="event-days.to"></strong></span> + <span>{{localizedEndDateForMultiDay}}</span> + <span class="text-muted" *ngIf="displayTimeZoneInfo"> ({{dateValidityProvider.timeZone}})</span> + </div> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/event-dates/event-dates.component.ts b/frontend/projects/public/src/app/event-dates/event-dates.component.ts new file mode 100644 index 0000000000..fff13d2f1c --- /dev/null +++ b/frontend/projects/public/src/app/event-dates/event-dates.component.ts @@ -0,0 +1,36 @@ +import {Component, Input} from '@angular/core'; +import {DateValidity} from '../model/date-validity'; +import {shouldDisplayTimeZoneInfo} from '../shared/event.service'; +import {TranslateService} from '@ngx-translate/core'; + + +@Component({ + selector: 'app-event-dates', + templateUrl: './event-dates.component.html' +}) +export class EventDatesComponent { + @Input() + dateValidityProvider: DateValidity; + @Input() + displayIcon: boolean; + + constructor(public translate: TranslateService) { } + + get displayTimeZoneInfo(): boolean { + return shouldDisplayTimeZoneInfo(this.dateValidityProvider); + } + + get localizedStartDateForMultiDay(): string { + return this.translate.instant('event-days.not-same-day', { + '0': this.dateValidityProvider.formattedBeginDate[this.translate.currentLang], + '1': this.dateValidityProvider.formattedBeginTime[this.translate.currentLang] + }); + } + + get localizedEndDateForMultiDay(): string { + return this.translate.instant('event-days.not-same-day', { + '0': this.dateValidityProvider.formattedEndDate[this.translate.currentLang], + '1': this.dateValidityProvider.formattedEndTime[this.translate.currentLang] + }); + } +} diff --git a/frontend/projects/public/src/app/event-display/event-display.component.html b/frontend/projects/public/src/app/event-display/event-display.component.html new file mode 100644 index 0000000000..37ec67654f --- /dev/null +++ b/frontend/projects/public/src/app/event-display/event-display.component.html @@ -0,0 +1,216 @@ +<app-purchase-context-container [purchaseContext]="event"> + <div *ngIf="event" class="add-margin-bottom"> + <header><app-purchase-context-header [purchaseContext]="event" type="event"></app-purchase-context-header></header> + <main> + + <hr> + + <div class="row"> + <div [ngClass]="{'col-md-7': displayMap, 'col-md-12': !displayMap}"> + <app-event-summary [event]="event" [dateValidityProvider]="event"></app-event-summary> + </div> + <div class="col-md-5" *ngIf="displayMap"><img [src]="event.mapUrl" class="img-responsive margin-auto" role="presentation" alt=""></div> + </div> + + <hr> + + <div class="markdown-content" [innerHTML]="event.description[translate.currentLang]"></div> + + <div #tickets></div> + <h2 class="mt-4 mb-4">{{'show-event.tickets' | translate}} <span *ngIf="ticketsLeftCountVisible()" class="badge badge-warning ms-2">{{"show-event.tickets.left" | translate: {'0': event.availableTicketsCount} }}</span></h2> + + <div class="alert alert-danger" role="alert" *ngIf="globalErrors && globalErrors.length > 0"> + <div *ngFor="let err of globalErrors"><strong>{{err.code | translate: err.arguments}}</strong></div> + </div> + + <div class="alert alert-success" role="alert" *ngIf="dynamicDiscountMessage"> + <span [innerHTML]="dynamicDiscountMessage"></span> + </div> + + <!-- begin waiting list --> + <div *ngIf="waitingList && !waitingListRequestSubmitted" class="alert alert-warning" role="alert"> + <ng-container *ngIf="preSales"> + <h3 class="wMarginBottom" translate="show-event.pre-sales.header"></h3> + <h4 translate="show-event.pre-sales.message"></h4> + </ng-container> + <ng-container *ngIf="!preSales"> + <h3 class="wMarginBottom" translate="show-event.sold-out.header"></h3> + <h4 translate="show-event.sold-out.message"></h4> + </ng-container> + <hr/> + <form [formGroup]="waitingListForm" (submit)="submitWaitingListRequest(event.shortName, waitingListForm.value)"> + <div class="form-group"> + <label class="form-label" for="firstName" translate="common.first-name"></label> + <input type="text" id="firstName" class="form-control" name="firstName" formControlName="firstName" required="required" aria-required="true" [attr.maxlength]="255" appInvalidFeedback> + </div> + <div class="form-group"> + <label class="form-label" for="lastName" translate="common.last-name"></label> + <input type="text" id="lastName" class="form-control" name="lastName" formControlName="lastName" required="required" aria-required="true" [attr.maxlength]="255" appInvalidFeedback> + </div> + <div class="form-group"> + <label class="form-label" for="email" translate="common.email"></label> + <input type="email" id="email" class="form-control" name="email" formControlName="email" required="required" aria-required="true" [attr.maxlength]="255" appInvalidFeedback> + </div> + <div class="form-group" *ngIf="ticketCategoriesForWaitingList && ticketCategoriesForWaitingList.length > 1"> + <label class="form-label" for="selectedCategory" translate="reservation-page.category"></label> + <select name="selectedCategory" id="selectedCategory" formControlName="selectedCategory" class="form-select" appInvalidFeedback> + <option value=""></option> + <option *ngFor="let tc of ticketCategoriesForWaitingList" [ngValue]="tc.id">{{tc.name}}</option> + </select> + </div> + <div class="form-group"> + <label class="form-label" for="userLanguage" translate="reservation-page-complete.language"></label> + <select name="userLanguage" formControlName="userLanguage" id="userLanguage" class="form-select" required="required" aria-required="true" appInvalidFeedback> + <option value=""></option> + <option *ngFor="let l of event.contentLanguages" [ngValue]="l.locale">{{l.displayLanguage}}</option> + </select> + </div> + <div class="form-check mt-2 wMarginBottom" *ngIf="event.privacyPolicyUrl"> + <input class="form-check-input" id="privacyPolicyAccepted" type="checkbox" required name="privacyPolicyAccepted" formControlName="privacyPolicyAccepted" value="true" appInvalidFeedback [invalidFeedbackInLabel]="true"> + <label class="form-check-label" for="privacyPolicyAccepted"> + {{'reservation-page.privacy.prefix'|translate}}{{' '}}<a [href]="event.privacyPolicyUrl" target="_blank" rel="noopener" translate="reservation-page.privacy.link.text"></a>{{' '}}{{'reservation-page.privacy.suffix'|translate}} + </label> + </div> + <div class="form-check"> + <input class="form-check-input" id="termsAndConditionsAccepted" type="checkbox" required name="termAndConditionsAccepted" formControlName="termAndConditionsAccepted" aria-required="true" appInvalidFeedback [invalidFeedbackInLabel]="true"> + <label class="form-check-label" for="termsAndConditionsAccepted"> + {{'reservation-page.tc.prefix'|translate}}{{' '}}<a [href]="event.termsAndConditionsUrl" target="_blank" rel="noopener" translate="reservation-page.tc.link.text"></a>{{' '}}{{'reservation-page.tc.suffix'|translate}} + </label> + </div> + <div class="row justify-content-end"> + <div class="col-md-4 col-12"> + <button type="submit" id="waiting-queue-subscribe" class="btn btn-success block-button " translate="show-event.sold-out.subscribe"></button> + </div> + </div> + </form> + </div> + <div *ngIf="waitingListRequestSubmitted"> + <div class="alert alert-danger" role="alert" *ngIf="!waitingListRequestResult"> + <h4 translate="show-event.sold-out.subscription-error"></h4> + </div> + <div class="alert alert-success" role="alert" *ngIf="waitingListRequestResult"> + <h4 translate="show-event.sold-out.subscription-complete"></h4> + </div> + </div> + <!-- end waiting list --> + + <form [formGroup]="reservationForm" (submit)="submitForm(event.shortName, reservationForm.value)" *ngIf="ticketCategories"> + <div class="alert alert-warning text-center" role="alert" *ngIf="ticketCategories.length == 0"> + <h3 translate="show-event.sold-out.header"></h3> + </div> + <div *ngFor="let category of ticketCategories;let counter = index" formArrayName="reservation"> + <app-item-card [currentLang]="translate.currentLang" [item]="category" [formGroupName]="''+counter" [parentFormGroup]="reservationFormItem(reservationForm, counter)" [additionalClass]="category.accessRestricted ? 'ticket-category-restricted border-warning shadow-sm ': ''"> + <span class="item-title"> + {{category.name}} + </span> + <div class="item-badges mb-2"> + <span *ngIf="displayOnlineTicketTag(category)" class="badge badge-success">{{'event.online.badge' | translate}}</span> + <span class="badge badge-warning" [class.ms-2]="displayOnlineTicketTag(category)" *ngIf="ticketsLeftCountVisibleForCategory(category)">{{"show-event.tickets.left" | translate: {'0': category.availableTickets} }}</span> + </div> + <div class="item-price"> + <span *ngIf="category.free" translate="common.free"></span> + <span *ngIf="!category.free"> + <app-price-tag [purchaseContext]="event" [formattedPrice]="category.formattedFinalPrice" [showTaxDetails]="category.displayTaxInformation" [discountedPrice]="category.formattedDiscountedPrice" [showDiscount]="category.hasDiscount"></app-price-tag> + </span> + </div> + <div class="item-qty-selector"> + <app-ticket-quantity-selector [parentGroup]="reservationFormItem(reservationForm, counter)" + [category]="category" + [quantityRange]="ticketCategoryAmount[category.id]" + (valueChange)="selectionChange()"></app-ticket-quantity-selector> + </div> + </app-item-card> + </div> + + <div class="mt-4 text-muted" *ngIf="expiredCategories.length > 0"> + <div class="text-center"> + <button type="button" (click)="expiredCategoriesExpanded = !expiredCategoriesExpanded" class="btn btn-link"><fa-icon [icon]="expiredCategoriesExpanded ? ['fas', 'angle-up'] : ['fas', 'angle-down']" a11yRole="presentation"></fa-icon> {{ 'show-event.expired-categories' | translate }}</button> + </div> + <ul class="list-group list-group-flush text-muted mt-2" *ngIf="expiredCategoriesExpanded"> + <li *ngFor="let category of expiredCategories;" class="list-group-item"> + <div class="d-flex flex-grow-1 justify-content-between"> + <div> + <div class="h6 pe-1"> + {{category.name}} + </div> + </div> + <div> + <div class="h6">{{'show-event.sales-ended'|translate: {'0': category.formattedExpiration[translate.currentLang]} }}</div> + </div> + </div> + </li> + </ul> + </div> + + + + <div *ngIf="supplementCategories && supplementCategories.length > 0" class="mt-5"> + <h2 translate="show-event.additional-services"></h2> + <app-additional-service *ngFor="let supplement of supplementCategories" [form]="reservationForm" [additionalService]="supplement" [event]="event"></app-additional-service> + </div> + + <div *ngIf="donationCategories && donationCategories.length > 0" class="mt-5"> + <h2 translate="show-event.donations"></h2> + <app-additional-service *ngFor="let donation of donationCategories" [form]="reservationForm" [additionalService]="donation" [event]="event"></app-additional-service> + </div> + + <div class="alert alert-light mt-4" *ngIf="eventCode"> + <div class="d-flex justify-content-between"> + <div class="h-100"> + <h5 class="alert-heading"><fa-icon [icon]="event.promotionsConfiguration.usePartnerCode ? ['far', 'handshake'] : ['fas', 'gift']" a11yRole="presentation"></fa-icon>{{' '}}{{'show-event.promo-code-applied' | translate: {'0': (event.promotionsConfiguration.usePartnerCode ? 'show-event.promo-code-type.partner' : 'show-event.promo-code-type.promotional') | translate} }} {{eventCode.code}}</h5> + <p *ngIf="eventCode.type === 'DISCOUNT'"> + <span *ngIf="eventCode.discountType === 'FIXED_AMOUNT'"> + {{'show-event.promo-code-fixed-amount-discount'|translate: {'0': eventCode.discountAmount + ' ' + event.currency} }} + </span> + <span *ngIf="eventCode.discountType === 'FIXED_AMOUNT_RESERVATION'"> + {{'show-event.promo-code-percentage-discount'|translate: {'0': eventCode.discountAmount + ' ' + event.currency} }} + </span> + <span *ngIf="eventCode.discountType === 'PERCENTAGE'"> + {{'show-event.promo-code-percentage-discount'|translate: {'0': eventCode.discountAmount + '%'} }} + </span> + </p> + </div> + <div> + <button class="btn btn-sm btn-light" type="button" (click)="removePromoCode()" translate="reservation-page.cancel"></button> + </div> + </div> + </div> + + + <div *ngIf="event.promotionsConfiguration.hasAccessPromotions" [ngClass]="{'alert alert-info': displayPromoCodeForm}" class="mt-4"> + <div class="d-flex justify-content-center justify-content-md-end" *ngIf="!displayPromoCodeForm && eventCode == null"> + <button type="button" class="btn btn-link" (click)="togglePromoCodeVisible()"> + <fa-icon [icon]="event.promotionsConfiguration.usePartnerCode ? ['far', 'handshake'] : ['fas', 'gift']" a11yRole="presentation"></fa-icon>{{' '}} + <span>{{'show-event.promo-code'|translate: {'0': (event.promotionsConfiguration.usePartnerCode ? 'show-event.promo-code-type.partner' : 'show-event.promo-code-type.promotional') | translate } }}</span> + </button> + </div> + <div *ngIf="displayPromoCodeForm" class="mt-2"> + <div class="row" [formGroup]="promoCodeForm"> + <div class="col-12 col-md-6 offset-md-1"> + <div class="form-group"> + <label for="promo-code" class="sr-only">{{'show-event.promo-code-insert' | translate: {'0': (event.promotionsConfiguration.usePartnerCode ? 'show-event.promo-code-type.partner' : 'show-event.promo-code-type.promotional') | translate } }}</label> + <input (keydown.enter)="promoCodeOnEnter($event)" required formControlName="promoCode" [placeholder]="'show-event.promo-code-insert' | translate: {'0': (event.promotionsConfiguration.usePartnerCode ? 'show-event.promo-code-type.partner' : 'show-event.promo-code-type.promotional') | translate }" autocomplete="off" class="form-control" id="promo-code" appInvalidFeedback #promoCode> + <div class="invalid-feedback"></div> + </div> + </div> + <div class="col-12 col-md-4 text-center"> + <button class="btn btn-success" translate="show-event.promo-code.apply" type="button" [disabled]="promoCodeForm.invalid" (click)="applyPromoCode()"></button> + <button class="btn btn-light ms-2" translate="reservation-page-complete.cancel" type="button" (click)="togglePromoCodeVisible()"></button> + </div> + </div> + </div> + </div> + + <app-recaptcha [apiKey]="event.captchaConfiguration.recaptchaApiKey" (recaptchaResponse)="handleRecaptchaResponse($event)" *ngIf="event.captchaConfiguration.captchaForTicketSelection && event.captchaConfiguration.recaptchaApiKey"></app-recaptcha> + + <hr class="mt-5"> + + <div class="row d-flex justify-content-between mobile-add-margin-bottom"> + <div class="col-md-5 order-md-1 col-12"><button type="submit" class="block-button btn btn-success" translate="show-event.continue"></button></div> + <div class="col-md-5 order-md-0 col-12 "><a [href]="event.websiteUrl" class="block-button btn btn-light" translate="to-event-site"></a></div> + </div> + </form> + + </main> + </div> +</app-purchase-context-container> diff --git a/frontend/projects/public/src/app/event-display/event-display.component.scss b/frontend/projects/public/src/app/event-display/event-display.component.scss new file mode 100644 index 0000000000..54036659c8 --- /dev/null +++ b/frontend/projects/public/src/app/event-display/event-display.component.scss @@ -0,0 +1,19 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; + +.promo-code-input { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.margin-auto { + margin: auto; +} + +.badge-block { + display: block; +} + +.text-muted .btn-link { + color: $text-muted; +} diff --git a/frontend/projects/public/src/app/event-display/event-display.component.ts b/frontend/projects/public/src/app/event-display/event-display.component.ts new file mode 100644 index 0000000000..22b1e040df --- /dev/null +++ b/frontend/projects/public/src/app/event-display/event-display.component.ts @@ -0,0 +1,301 @@ +import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {EventService} from '../shared/event.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {UntypedFormArray, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; +import {ReservationService} from '../shared/reservation.service'; +import {Event as AlfioEvent} from '../model/event'; +import {TranslateService} from '@ngx-translate/core'; +import {TicketCategory} from '../model/ticket-category'; +import {ReservationRequest} from '../model/reservation-request'; +import {handleServerSideValidationError} from '../shared/validation-helper'; +import {zip} from 'rxjs'; +import {AdditionalService} from '../model/additional-service'; +import {I18nService} from '../shared/i18n.service'; +import {WaitingListSubscriptionRequest} from '../model/waiting-list-subscription-request'; +import {ItemsByCategory, TicketCategoryForWaitingList} from '../model/items-by-category'; +import {DynamicDiscount, EventCode} from '../model/event-code'; +import {AnalyticsService} from '../shared/analytics.service'; +import {ErrorDescriptor} from '../model/validated-response'; +import {SearchParams} from '../model/search-params'; + +@Component({ + selector: 'app-event-display', + templateUrl: './event-display.component.html', + styleUrls: ['./event-display.component.scss'] +}) +export class EventDisplayComponent implements OnInit { + + event: AlfioEvent; + ticketCategories: TicketCategory[]; + expiredCategories: TicketCategory[]; + // + supplementCategories: AdditionalService[]; + donationCategories: AdditionalService[]; + // + reservationForm: UntypedFormGroup; + globalErrors: ErrorDescriptor[] = []; + // + ticketCategoryAmount: {[key: number]: number[]}; + // + + // + preSales: boolean; + waitingList: boolean; + ticketCategoriesForWaitingList: TicketCategoryForWaitingList[]; + waitingListForm: UntypedFormGroup; + waitingListRequestSubmitted: boolean; + waitingListRequestResult: boolean; + // + + eventCode: EventCode; + eventCodeError: boolean; + + displayPromoCodeForm: boolean; + promoCodeForm: UntypedFormGroup; + @ViewChild('promoCode') + promoCodeElement: ElementRef<HTMLInputElement>; + @ViewChild('tickets') + tickets: ElementRef<HTMLDivElement>; + expiredCategoriesExpanded = false; + + private dynamicDiscount: DynamicDiscount; + + // https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/ + + constructor( + private route: ActivatedRoute, + private router: Router, + private eventService: EventService, + private reservationService: ReservationService, + private formBuilder: UntypedFormBuilder, + public translate: TranslateService, + private i18nService: I18nService, + private analytics: AnalyticsService) { } + + ngOnInit(): void { + + const code = this.route.snapshot.queryParams['code']; + const errors = this.route.snapshot.queryParams['errors']; + if (errors) { + this.globalErrors = errors.split(',').map(val => { const ed = new ErrorDescriptor(); ed.code = val; return ed; }); + } + + this.route.params.subscribe(params => { + const eventShortName = params['eventShortName']; + + zip(this.eventService.getEvent(eventShortName), this.eventService.getEventTicketsInfo(eventShortName)).subscribe( ([event, itemsByCat]) => { + this.event = event; + + this.i18nService.setPageTitle('show-event.header.title', event); + + this.reservationForm = this.formBuilder.group({ + reservation: this.formBuilder.array(this.createItems(itemsByCat.ticketCategories)), + additionalService: this.formBuilder.array([]), + captcha: null, + promoCode: null + }); + + this.promoCodeForm = this.formBuilder.group({ + promoCode: this.formBuilder.control(code) + }); + + this.applyItemsByCat(itemsByCat); + this.analytics.pageView(event.analyticsConfiguration); + + if (code) { + this.internalApplyPromoCode(code, err => this.globalErrors = err); + } + }); + }); + } + + private applyItemsByCat(itemsByCat: ItemsByCategory) { + this.ticketCategories = itemsByCat.ticketCategories; + this.expiredCategories = itemsByCat.expiredCategories || []; + + this.ticketCategoryAmount = {}; + this.ticketCategories.forEach(tc => { + this.ticketCategoryAmount[tc.id] = []; + for (let i = 0; i <= tc.maximumSaleableTickets; i++) { + this.ticketCategoryAmount[tc.id].push(i); + } + }); + + this.supplementCategories = itemsByCat.additionalServices.filter(e => e.type === 'SUPPLEMENT'); + this.donationCategories = itemsByCat.additionalServices.filter(e => e.type === 'DONATION'); + + this.preSales = itemsByCat.preSales; + this.waitingList = itemsByCat.waitingList; + this.ticketCategoriesForWaitingList = itemsByCat.ticketCategoriesForWaitingList; + + this.createWaitingListFormIfNecessary(); + } + + private createWaitingListFormIfNecessary() { + if (this.waitingList && !this.waitingListForm) { + this.waitingListForm = this.formBuilder.group({ + firstName: null, + lastName: null, + email: null, + selectedCategory: null, + userLanguage: null, + termAndConditionsAccepted: null, + privacyPolicyAccepted: null + }); + } + } + + private createItems(ticketCategories: TicketCategory[]): UntypedFormGroup[] { + return ticketCategories.map(category => this.formBuilder.group({ticketCategoryId: category.id, amount: 0})); + } + + submitForm(eventShortName: string, reservation: ReservationRequest) { + const request = reservation; + if (reservation.additionalService != null && reservation.additionalService.length > 0) { + request.additionalService = reservation.additionalService.filter(as => (as.amount != null && as.amount > 0) || (as.quantity != null && as.quantity > 0)); + } + this.reservationService.reserveTickets(eventShortName, request, this.translate.currentLang).subscribe(res => { + if (res.success) { + this.router.navigate(['event', eventShortName, 'reservation', res.value, 'book'], { + queryParams: SearchParams.transformParams(this.route.snapshot.queryParams, this.route.snapshot.params) + }); + } + }, (err) => { + this.globalErrors = handleServerSideValidationError(err, this.reservationForm); + this.scrollToTickets(); + }); + } + + private scrollToTickets(): void { + setTimeout(() => { + if (this.tickets != null && this.tickets.nativeElement != null) { + this.tickets.nativeElement.scrollIntoView(true); + } + }, 10); + } + + submitWaitingListRequest(eventShortName: string, waitingListSubscriptionRequest: WaitingListSubscriptionRequest) { + + this.eventService.submitWaitingListSubscriptionRequest(eventShortName, waitingListSubscriptionRequest).subscribe(res => { + this.waitingListRequestSubmitted = true; + this.waitingListRequestResult = res.value; + }, (err) => { + this.globalErrors = handleServerSideValidationError(err, this.waitingListForm); + }); + } + + handleRecaptchaResponse(recaptchaValue: string): void { + this.reservationForm.get('captcha').setValue(recaptchaValue); + } + + private internalApplyPromoCode(promoCode: string, errorHandler: ((errors: ErrorDescriptor[]) => void)): void { + this.globalErrors = []; + this.eventCodeError = false; + + if (promoCode === null || promoCode === undefined || promoCode.trim() === '') { + return; + } + + this.eventService.validateCode(this.event.shortName, promoCode).subscribe(res => { + if (res.success) { + // this.router.navigate([], {relativeTo: this.route, queryParams: {code: promoCode}, queryParamsHandling: "merge"}) + // TODO, set promo code in url, fetch ticket category, rebuild the reservationForm.reservation + + // + this.reloadTicketsInfo(promoCode, res.value); + this.displayPromoCodeForm = false; + // + } else { + this.eventCode = null; // should never enter here + this.reservationForm.get('promoCode').setValue(null); + } + }, (err) => { + errorHandler(handleServerSideValidationError(err, this.promoCodeForm)); + this.eventCode = null; + this.reloadTicketsInfo(null, null); + this.eventCodeError = true; + }); + } + + applyPromoCode(): void { + const promoCode = this.promoCodeForm.get('promoCode').value; + this.globalErrors = []; + this.internalApplyPromoCode(promoCode, () => {}); + } + + removePromoCode(): void { + this.reloadTicketsInfo(null, null); + } + + togglePromoCodeVisible(): void { + this.displayPromoCodeForm = !this.displayPromoCodeForm; + if (this.displayPromoCodeForm) { + setTimeout(() => this.promoCodeElement.nativeElement.focus(), 200); + } else { + this.promoCodeForm.get('promoCode').setValue(null); + } + } + + ticketsLeftCountVisible(): boolean { + return this.event.availableTicketsCount != null + && this.event.availableTicketsCount > 0 + && this.ticketCategories.every(tc => !tc.bounded); + } + + reservationFormItem(parent: UntypedFormGroup, counter: number): UntypedFormGroup { + return (parent.get('reservation') as UntypedFormArray).at(counter) as UntypedFormGroup; + } + + ticketsLeftCountVisibleForCategory(category: TicketCategory): boolean { + return category.availableTickets != null && category.availableTickets > 0; + } + + private reloadTicketsInfo(promoCode: string, eventCode: EventCode) { + this.eventService.getEventTicketsInfo(this.event.shortName, promoCode).subscribe(itemsByCat => { + this.reservationForm.get('promoCode').setValue(promoCode); + this.reservationForm.setControl('reservation', this.formBuilder.array(this.createItems(itemsByCat.ticketCategories))); + this.applyItemsByCat(itemsByCat); + this.eventCode = eventCode; + if (eventCode != null) { + this.scrollToTickets(); + } + }); + } + + promoCodeOnEnter(ev: Event) { + ev.preventDefault(); + if (this.promoCodeForm.invalid) { + return; + } + this.applyPromoCode(); + } + + selectionChange(): void { + if (this.eventCode == null || this.eventCode.type === 'ACCESS') { + this.reservationService.checkDynamicDiscountAvailability(this.event.shortName, this.reservationForm.value) + .subscribe(d => { + this.dynamicDiscount = d; + }); + } + } + + get dynamicDiscountMessage(): string { + if (this.dynamicDiscount != null) { + return this.dynamicDiscount.formattedMessage[this.translate.currentLang]; + } + return null; + } + + get isEventOnline(): boolean { + return this.event.format === 'ONLINE'; + } + + public displayOnlineTicketTag(category: TicketCategory): boolean { + return this.event.format === 'HYBRID' && category.ticketAccessType === 'ONLINE'; + } + + get displayMap(): boolean { + return (this.event.mapUrl && this.event.mapUrl.length > 0) && !this.isEventOnline; + } + +} diff --git a/frontend/projects/public/src/app/event-footer-links/footer-links.component.html b/frontend/projects/public/src/app/event-footer-links/footer-links.component.html new file mode 100644 index 0000000000..f1baca0b7d --- /dev/null +++ b/frontend/projects/public/src/app/event-footer-links/footer-links.component.html @@ -0,0 +1,9 @@ +<div [ngClass]="marginClass" class="d-md-flex text-center text-md-left justify-content-md-center"> + <div *ngIf="linksContainer?.termsAndConditionsUrl"> + <a [href]="linksContainer.termsAndConditionsUrl" target="_blank" rel="noopener" + [attr.aria-label]="('reservation-page.tc.link.text' | translate) + ('link.new-tab' | translate)">{{'reservation-page.tc.link.text'|translate}}</a></div> + <div class="ms-3 me-3 d-none d-md-block" *ngIf="linksContainer?.termsAndConditionsUrl && linksContainer?.privacyPolicyUrl">–</div> + <div *ngIf="linksContainer?.privacyPolicyUrl"> + <a [href]="linksContainer.privacyPolicyUrl" target="_blank" rel="noopener" + [attr.aria-label]="('reservation-page.privacy.link.text'|translate) + ('link.new-tab' | translate)">{{'reservation-page.privacy.link.text'|translate}}</a></div> +</div> diff --git a/frontend/projects/public/src/app/event-footer-links/footer-links.component.ts b/frontend/projects/public/src/app/event-footer-links/footer-links.component.ts new file mode 100644 index 0000000000..7df000f97c --- /dev/null +++ b/frontend/projects/public/src/app/event-footer-links/footer-links.component.ts @@ -0,0 +1,14 @@ +import {Component, Input} from '@angular/core'; +import {TermsPrivacyLinksContainer} from '../model/event'; + +@Component({ + selector: 'app-footer-links', + templateUrl: './footer-links.component.html' +}) +export class FooterLinksComponent { + @Input() + marginClass = 'mb-5'; + + @Input() + linksContainer?: TermsPrivacyLinksContainer; +} diff --git a/frontend/projects/public/src/app/event-list-all/event-list-all.component.html b/frontend/projects/public/src/app/event-list-all/event-list-all.component.html new file mode 100644 index 0000000000..dccdde1f7f --- /dev/null +++ b/frontend/projects/public/src/app/event-list-all/event-list-all.component.html @@ -0,0 +1,18 @@ +<div class="container-xl"> + <div *ngIf="events && languages" class="application-container p-2 p-md-5"> + <app-topbar [contentLanguages]="languages"></app-topbar> + + <h1 translate="event-list.title"></h1> + + <div class="event-list-card mb-3" *ngFor="let event of events"> + <app-basic-event-info [event]="event" [params]="queryParams"></app-basic-event-info> + </div> + + <div class="alert text-center" *ngIf="events.length == 0"> + <h3 translate="event-list.no-events"></h3> + </div> + + <hr class="mt-5" *ngIf="linksContainer.termsAndConditionsUrl || linksContainer.privacyPolicyUrl"> + <app-footer-links [linksContainer]="linksContainer"></app-footer-links> + </div> +</div> diff --git a/frontend/projects/public/src/app/event-list-all/event-list-all.component.scss b/frontend/projects/public/src/app/event-list-all/event-list-all.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/public/src/app/event-list-all/event-list-all.component.ts b/frontend/projects/public/src/app/event-list-all/event-list-all.component.ts new file mode 100644 index 0000000000..cca630d8a6 --- /dev/null +++ b/frontend/projects/public/src/app/event-list-all/event-list-all.component.ts @@ -0,0 +1,57 @@ +import {Component, OnInit} from '@angular/core'; +import {BasicEventInfo} from '../model/basic-event-info'; +import {Language, TermsPrivacyLinksContainer} from '../model/event'; +import {EventService} from '../shared/event.service'; +import {I18nService} from '../shared/i18n.service'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {InfoService} from '../shared/info.service'; +import {AnalyticsService} from '../shared/analytics.service'; +import {TranslateService} from '@ngx-translate/core'; +import {of, zip} from 'rxjs'; +import {mergeMap} from 'rxjs/operators'; +import {SearchParams} from '../model/search-params'; +import {globalTermsPrivacyLinks} from '../model/info'; +import {filterAvailableLanguages} from '../model/purchase-context'; + +@Component({ + selector: 'app-event-list-all', + templateUrl: './event-list-all.component.html', + styleUrls: ['./event-list-all.component.scss'] +}) +export class EventListAllComponent implements OnInit { + + events: BasicEventInfo[]; + languages: Language[]; + queryParams: Params; + linksContainer: TermsPrivacyLinksContainer; + + constructor( + private eventService: EventService, + private i18nService: I18nService, + private router: Router, + public translate: TranslateService, + private info: InfoService, + private analytics: AnalyticsService, + private route: ActivatedRoute) { } + + public ngOnInit(): void { + + zip(this.route.queryParams, this.route.params).pipe( + mergeMap(([params, pathParams]) => { + const searchParams = SearchParams.fromQueryAndPathParams(params, pathParams); + return zip(this.eventService.getEvents(searchParams), this.info.getInfo(), of(searchParams), this.i18nService.getAvailableLanguages()); + }) + ).subscribe(([res, info, searchParams, activeLanguages]) => { + this.queryParams = searchParams.toParams(); + if (res.length === 1) { + this.router.navigate(['/event', res[0].shortName], {replaceUrl: true, queryParams: this.queryParams}); + } else { + this.events = res; + this.analytics.pageView(info.analyticsConfiguration); + this.linksContainer = globalTermsPrivacyLinks(info); + this.languages = filterAvailableLanguages(activeLanguages, res); + this.i18nService.setPageTitle('event-list.header.title', null); + } + }); + } +} diff --git a/frontend/projects/public/src/app/event-summary/event-summary.component.html b/frontend/projects/public/src/app/event-summary/event-summary.component.html new file mode 100644 index 0000000000..4e11fad1f9 --- /dev/null +++ b/frontend/projects/public/src/app/event-summary/event-summary.component.html @@ -0,0 +1,25 @@ +<div *ngIf="event && dateValidityProvider"> + <h2 translate="ticket.event-info" class="mt-4 mb-4 d-none d-md-block"></h2> + + <div> + <dl class="row"> + <dt class="col-1 text-muted"><fa-icon [icon]="['far', 'building']" a11yRole="presentation"></fa-icon></dt> + <dd class="col-11">{{'show-event.by'|translate}} <strong><a [href]="'mailto:'+ event.organizationEmail">{{event.organizationName}}</a></strong></dd> + <dt class="col-1 text-muted"><fa-icon [icon]="['far', 'calendar-alt']" [title]="'ticket.date-time' | translate"></fa-icon></dt> + <dd class="col-11"> + <app-event-dates [dateValidityProvider]="dateValidityProvider"></app-event-dates> + </dd> + <dt class="col-1 text-muted"><fa-icon [icon]="['far', 'calendar-plus']" a11yRole="presentation"></fa-icon></dt> + <dd class="col-11"> + {{'show-event.add-to-calendar' | translate}} + <a target="_blank" rel="noopener" href="/api/v2/public/event/{{event.shortName}}/calendar/{{translate.currentLang}}?type=google">Google</a> + – + <a target="_blank" rel="noopener" href="/api/v2/public/event/{{event.shortName}}/calendar/{{translate.currentLang}}">iCalendar</a> + </dd> + <dt class="col-1 text-muted"><fa-icon [icon]="['far', 'compass']" [title]="(isEventOnline ? 'event.location.online' : 'ticket.location') | translate"></fa-icon></dt> + <dd class="col-11" *ngIf="!isEventOnline">{{ event.location }}</dd> + <dd class="col-11" *ngIf="isEventOnline"><div class="h6" *ngIf="isEventOnline"><span class="badge badge-secondary">{{'event.online.badge' | translate}}</span></div></dd> + + </dl> + </div> +</div> diff --git a/frontend/projects/public/src/app/event-summary/event-summary.component.ts b/frontend/projects/public/src/app/event-summary/event-summary.component.ts new file mode 100644 index 0000000000..96d2ccf002 --- /dev/null +++ b/frontend/projects/public/src/app/event-summary/event-summary.component.ts @@ -0,0 +1,24 @@ +import {Component, Input} from '@angular/core'; +import {Event} from '../model/event'; +import {TranslateService} from '@ngx-translate/core'; +import {DateValidity} from '../model/date-validity'; + +@Component({ + selector: 'app-event-summary', + templateUrl: './event-summary.component.html' +}) +export class EventSummaryComponent { + + @Input() + event: Event; + + @Input() + dateValidityProvider: DateValidity; + + constructor(public translate: TranslateService) { } + + get isEventOnline(): boolean { + return this.event.format === 'ONLINE'; + } + +} diff --git a/frontend/projects/public/src/app/event.guard.ts b/frontend/projects/public/src/app/event.guard.ts new file mode 100644 index 0000000000..f854007cc5 --- /dev/null +++ b/frontend/projects/public/src/app/event.guard.ts @@ -0,0 +1,25 @@ +import {Injectable} from '@angular/core'; +import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; +import {Observable, of} from 'rxjs'; +import {EventService} from './shared/event.service'; +import {catchError, map, tap} from 'rxjs/operators'; +import {handleCustomCss} from './shared/custom-css-helper'; + +@Injectable({ + providedIn: 'root' +}) +export class EventGuard implements CanActivate { + + constructor(private eventService: EventService, private router: Router) { + } + + canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> { + const eventShortName = next.params['eventShortName']; + return this.eventService.getEvent(eventShortName) + .pipe( + tap((e) => handleCustomCss(e)), + catchError(e => of(this.router.parseUrl(''))), + map(e => e instanceof UrlTree ? e : true) + ); + } +} diff --git a/frontend/projects/public/src/app/home/home.component.html b/frontend/projects/public/src/app/home/home.component.html new file mode 100644 index 0000000000..1411f55eb8 --- /dev/null +++ b/frontend/projects/public/src/app/home/home.component.html @@ -0,0 +1,42 @@ +<div class="container-xl"> + <div *ngIf="events" class="application-container p-2 p-md-5"> + <app-topbar [contentLanguages]="languages"></app-topbar> + + <div *ngIf="containsEvents || !containsSubscriptions" class="mb-2"> + <h1 translate="event-list.title"></h1> + + <div class="row justify-content-center mb-3 mt-3"> + <div class="col-md-5" *ngFor="let event of events"> + <app-basic-event-info [event]="event"></app-basic-event-info> + </div> + </div> + <div *ngIf="displayViewAllEventsButton" class="text-center mt-5 mb-5"> + <a [routerLink]="allEventsPath" class="btn btn-default" translate="home.view.all.events"></a> + </div> + + + <div class="alert text-center" *ngIf="events.length == 0"> + <div class="h3" translate="event-list.no-events"></div> + </div> + </div> + + <hr *ngIf="containsEvents && containsSubscriptions" class="mt-5 mb-5"> + + <div *ngIf="containsSubscriptions" class="mb-2"> + <h1 translate="subscription.title"></h1> + + <div class="row justify-content-center mb-3 mt-3"> + <div class="col-md-5" *ngFor="let subscription of subscriptions"> + <app-basic-subscription-info [subscription]="subscription"></app-basic-subscription-info> + </div> + </div> + + <div *ngIf="displayViewAllSubscriptionsButton" class="text-center mt-5 mb-5"> + <a [routerLink]="allSubscriptionsPath" class="btn btn-default" translate="home.view.all.subscriptions"></a> + </div> + </div> + + <hr class="mt-5" *ngIf="linksContainer.termsAndConditionsUrl || linksContainer.privacyPolicyUrl"> + <app-footer-links [linksContainer]="linksContainer"></app-footer-links> + </div> +</div> diff --git a/frontend/projects/public/src/app/home/home.component.scss b/frontend/projects/public/src/app/home/home.component.scss new file mode 100644 index 0000000000..71e58d1480 --- /dev/null +++ b/frontend/projects/public/src/app/home/home.component.scss @@ -0,0 +1,3 @@ +.card-light:hover { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; +} diff --git a/frontend/projects/public/src/app/home/home.component.ts b/frontend/projects/public/src/app/home/home.component.ts new file mode 100644 index 0000000000..e329682b91 --- /dev/null +++ b/frontend/projects/public/src/app/home/home.component.ts @@ -0,0 +1,101 @@ +import {Component, OnInit} from '@angular/core'; +import {EventService} from '../shared/event.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {BasicEventInfo} from '../model/basic-event-info'; +import {I18nService} from '../shared/i18n.service'; +import {Language, TermsPrivacyLinksContainer} from '../model/event'; +import {TranslateService} from '@ngx-translate/core'; +import {AnalyticsService} from '../shared/analytics.service'; +import {InfoService} from '../shared/info.service'; +import {zip} from 'rxjs'; +import {SubscriptionService} from '../shared/subscription.service'; +import {BasicSubscriptionInfo} from '../model/subscription'; +import {mergeMap} from 'rxjs/operators'; +import {SearchParams} from '../model/search-params'; +import {globalTermsPrivacyLinks} from '../model/info'; +import {filterAvailableLanguages} from '../model/purchase-context'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] +}) +export class HomeComponent implements OnInit { + + events: BasicEventInfo[]; + allEvents: BasicEventInfo[]; + subscriptions: BasicSubscriptionInfo[]; + allSubscriptions: BasicSubscriptionInfo[]; + languages: Language[]; + linksContainer: TermsPrivacyLinksContainer; + private searchParams?: SearchParams; + + constructor( + private eventService: EventService, + private subscriptionService: SubscriptionService, + private i18nService: I18nService, + private router: Router, + public translate: TranslateService, + private info: InfoService, + private analytics: AnalyticsService, + private route: ActivatedRoute) { } + + public ngOnInit(): void { + zip(this.route.params, this.route.queryParams).pipe( + mergeMap(([pathParams, queryParams]) => { + this.searchParams = SearchParams.fromQueryAndPathParams(queryParams, pathParams); + return zip( + this.eventService.getEvents(this.searchParams), + this.subscriptionService.getSubscriptions(this.searchParams), + this.info.getInfo(), + this.i18nService.getAvailableLanguages()); + }) + ).subscribe(([res, subscriptions, info, activeLanguages]) => { + if (res.length === 1 && subscriptions.length === 0) { + this.router.navigate(['/event', res[0].shortName], {replaceUrl: true}); + } else { + this.allEvents = res; + this.events = res.slice(0, 4); + this.allSubscriptions = subscriptions; + this.subscriptions = subscriptions.slice(0, 4); + this.analytics.pageView(info.analyticsConfiguration); + this.linksContainer = globalTermsPrivacyLinks(info); + const allPurchaseContexts = [...res, ...subscriptions]; + this.languages = filterAvailableLanguages(activeLanguages, allPurchaseContexts); + } + }); + + this.i18nService.setPageTitle('event-list.header.title', null); + } + + get containsEvents(): boolean { + return this.events != null && this.events.length > 0; + } + + get displayViewAllEventsButton() { + return this.allEvents.length > 4; + } + + get containsSubscriptions(): boolean { + return this.subscriptions != null && this.subscriptions.length > 0; + } + + get displayViewAllSubscriptionsButton() { + return this.allSubscriptions.length > 4; + } + + get allEventsPath(): Array<string> { + if (this.searchParams?.organizerSlug != null) { + return ['/o', this.searchParams.organizerSlug, 'events-all']; + } + return ['events-all']; + } + + get allSubscriptionsPath(): Array<string> { + if (this.searchParams?.organizerSlug != null) { + return ['/o', this.searchParams.organizerSlug, 'subscriptions-all']; + } + return ['subscriptions-all']; + } + +} diff --git a/frontend/projects/public/src/app/item-card/item-card.component.ts b/frontend/projects/public/src/app/item-card/item-card.component.ts new file mode 100644 index 0000000000..8b417f7599 --- /dev/null +++ b/frontend/projects/public/src/app/item-card/item-card.component.ts @@ -0,0 +1,27 @@ +import {Component, Input} from '@angular/core'; +import {UntypedFormGroup} from '@angular/forms'; +import {TicketCategory} from '../model/ticket-category'; +import {AdditionalService} from '../model/additional-service'; +import {BasicEventInfo} from '../model/basic-event-info'; + +@Component({ + selector: 'app-item-card', + templateUrl: './item-card.html', + styleUrls: ['./item-card.scss'] +}) +export class ItemCardComponent { + @Input() + parentFormGroup: UntypedFormGroup; + + @Input() + item: TicketCategory | AdditionalService; + + @Input() + event: BasicEventInfo; + + @Input() + additionalClass = ''; + + @Input() + currentLang: string; +} diff --git a/frontend/projects/public/src/app/item-card/item-card.html b/frontend/projects/public/src/app/item-card/item-card.html new file mode 100644 index 0000000000..2d708b642e --- /dev/null +++ b/frontend/projects/public/src/app/item-card/item-card.html @@ -0,0 +1,23 @@ +<div class="card mt-4" [formGroup]="parentFormGroup" [ngClass]="additionalClass"> + <div class="card-body"> + <div class="item-content-except-desc"> + <h3 class="card-title pe-1"> + <ng-content select=".item-title"></ng-content> + </h3> + <div class="card-sale-period"> + <ng-content select=".item-badges"></ng-content> + <app-item-sale-period [item]="item" [currentLang]="currentLang"></app-item-sale-period> + </div> + <div class="card-item-price"> + <ng-content select=".item-price"></ng-content> + </div> + <div class="card-qty-container"> + <ng-content select=".item-qty-selector"></ng-content> + </div> + </div> + <hr class="d-md-none" *ngIf="item.description[currentLang] && item.description[currentLang].length > 0" /> + <div class="card-text pt-2"> + <div class="markdown-content mt-2" [innerHTML]="item.description[currentLang]"></div> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/item-card/item-card.scss b/frontend/projects/public/src/app/item-card/item-card.scss new file mode 100644 index 0000000000..ab8f8824d4 --- /dev/null +++ b/frontend/projects/public/src/app/item-card/item-card.scss @@ -0,0 +1,48 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/variables"; + + +.item-content-except-desc { + display: flex; + flex-wrap: wrap; +} + +@include media-breakpoint-up(md) { + + /* desktop here */ + .card-title { + order: 0; + max-width: 65%; + } + + .card-sale-period { + order: 3; + flex-basis: 100%; + } + + .card-item-price { + order: 1; + padding-right: 3rem!important; + margin-left: auto; + display: flex; + align-items: center; + } + + .card-qty-container { + order: 2; + @include make-col(2); + } +} + +@include media-breakpoint-down(sm) { + + /* mobile here */ + .item-content-except-desc { + flex-direction: column; + } + + .card-qty-container { + margin-top: 1rem; + } +} diff --git a/frontend/projects/public/src/app/language.guard.ts b/frontend/projects/public/src/app/language.guard.ts new file mode 100644 index 0000000000..aa8064baee --- /dev/null +++ b/frontend/projects/public/src/app/language.guard.ts @@ -0,0 +1,57 @@ +import {Injectable} from '@angular/core'; +import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router'; +import {Observable} from 'rxjs'; +import {I18nService} from './shared/i18n.service'; +import {TranslateService} from '@ngx-translate/core'; +import {catchError, map, switchMap} from 'rxjs/operators'; +import {PurchaseContextService, PurchaseContextType} from './shared/purchase-context.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LanguageGuard implements CanActivate { + + constructor(private i18nService: I18nService, private purchaseContextService: PurchaseContextService, private translate: TranslateService) { + } + + canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { + + const langQueryParam = next.queryParams['lang']; + const persisted = this.i18nService.getPersistedLanguage(); + + const type: PurchaseContextType = next.data.type; + const publicIdentifier = next.params[next.data.publicIdentifierParameter]; + const req = type && publicIdentifier ? this.getForContext(type, publicIdentifier) : this.getForApp(); + + return req.pipe(switchMap(availableLanguages => { + const lang = this.extractLang(availableLanguages, persisted, langQueryParam); + return this.i18nService.useTranslation(type, publicIdentifier, lang); + })); + } + + private getForContext(type: PurchaseContextType, publicIdentifier: string): Observable<string[]> { + return this.purchaseContextService.getContext(type, publicIdentifier).pipe(map(p => p.contentLanguages.map(v => v.locale))).pipe(catchError(val => this.getForApp())); + } + + private getForApp(): Observable<string[]> { + return this.i18nService.getAvailableLanguages().pipe(map(languages => languages.map(l => l.locale))); + } + + private extractLang(availableLanguages: string[], persisted: string, override: string): string { + let lang: string; + if (override && availableLanguages.indexOf(override) >= 0) { + lang = override; + } else if (availableLanguages.indexOf(persisted) >= 0) { + lang = persisted; + } else if (availableLanguages.indexOf(this.translate.getBrowserLang()) >= 0) { + lang = this.translate.getBrowserLang(); + } else if (availableLanguages.indexOf('en') >= 0) { + // use english as a default choice if available in case of mismatching browser lang + lang = 'en'; + } else { + lang = availableLanguages[0]; + } + return lang; + } + +} diff --git a/frontend/projects/public/src/app/model/additional-service.ts b/frontend/projects/public/src/app/model/additional-service.ts new file mode 100644 index 0000000000..f4211177e3 --- /dev/null +++ b/frontend/projects/public/src/app/model/additional-service.ts @@ -0,0 +1,31 @@ +export class AdditionalService { + id: number; + type: AdditionalServiceType; + supplementPolicy: SupplementPolicy; + // + fixPrice: boolean; + availableQuantity: number; + maxQtyPerOrder: number; + + // + free: boolean; + formattedFinalPrice: string; + hasDiscount: boolean; + formattedDiscountedPrice: string; + vatApplies: boolean; + vatIncluded: boolean; + vatPercentage: string; + // + + // + expired: boolean; + saleInFuture: boolean; + formattedInception: {[key: string]: string}; + formattedExpiration: {[key: string]: string}; + title: {[key: string]: string}; + description: {[key: string]: string}; +} + +export type AdditionalServiceType = 'DONATION' | 'SUPPLEMENT'; +export type SupplementPolicy = 'MANDATORY_ONE_FOR_TICKET' | 'OPTIONAL_UNLIMITED_AMOUNT' | 'OPTIONAL_MAX_AMOUNT_PER_TICKET' | + 'OPTIONAL_MAX_AMOUNT_PER_RESERVATION'; diff --git a/frontend/projects/public/src/app/model/analytics-configuration.ts b/frontend/projects/public/src/app/model/analytics-configuration.ts new file mode 100644 index 0000000000..c477f1c818 --- /dev/null +++ b/frontend/projects/public/src/app/model/analytics-configuration.ts @@ -0,0 +1,5 @@ +export class AnalyticsConfiguration { + googleAnalyticsKey: string | null; + googleAnalyticsScrambledInfo: boolean; + clientId: string | null; +} diff --git a/frontend/projects/public/src/app/model/basic-event-info.ts b/frontend/projects/public/src/app/model/basic-event-info.ts new file mode 100644 index 0000000000..86826c49fd --- /dev/null +++ b/frontend/projects/public/src/app/model/basic-event-info.ts @@ -0,0 +1,22 @@ +import {DatesWithOffset, DateValidity} from './date-validity'; +import {EventFormat, Language} from './event'; +import {Localized} from './purchase-context'; + +export class BasicEventInfo implements DateValidity, Localized { + shortName: string; + fileBlobId: string; + format: EventFormat; + title: {[key: string]: string}; + location: string; + + // date related + timeZone: string; + sameDay: boolean; + datesWithOffset: DatesWithOffset; + formattedBeginDate: {[key: string]: string}; // day, month, year + formattedBeginTime: {[key: string]: string}; // the hour/minute component + formattedEndDate: {[key: string]: string}; + formattedEndTime: {[key: string]: string}; + // + contentLanguages: Language[] = []; +} diff --git a/frontend/projects/public/src/app/model/date-validity.ts b/frontend/projects/public/src/app/model/date-validity.ts new file mode 100644 index 0000000000..d06ef22612 --- /dev/null +++ b/frontend/projects/public/src/app/model/date-validity.ts @@ -0,0 +1,16 @@ +export interface DateValidity { + timeZone: string; + sameDay: boolean; + datesWithOffset: DatesWithOffset; + formattedBeginDate: {[key: string]: string}; // day, month, year + formattedBeginTime: {[key: string]: string}; // the hour/minute component + formattedEndDate: {[key: string]: string}; + formattedEndTime: {[key: string]: string}; +} + +export interface DatesWithOffset { + startDateTime: number; + startTimeZoneOffset: number; + endDateTime: number; + endTimeZoneOffset: number; +} diff --git a/frontend/projects/public/src/app/model/embedding-configuration.ts b/frontend/projects/public/src/app/model/embedding-configuration.ts new file mode 100644 index 0000000000..6289937f2f --- /dev/null +++ b/frontend/projects/public/src/app/model/embedding-configuration.ts @@ -0,0 +1,10 @@ +import {ReservationStatus} from './reservation-info'; + +export class ReservationStatusChanged { + constructor(public status: ReservationStatus, public id: string, public error?: string) {} +} + +export interface EmbeddingConfiguration { + enabled: boolean; + notificationOrigin: string; +} diff --git a/frontend/projects/public/src/app/model/event-code.ts b/frontend/projects/public/src/app/model/event-code.ts new file mode 100644 index 0000000000..7a506e68ad --- /dev/null +++ b/frontend/projects/public/src/app/model/event-code.ts @@ -0,0 +1,15 @@ +export class EventCode { + code: string; + type: EventCodeType; + discountType: DiscountType; + discountAmount: string; +} + +export type EventCodeType = 'SPECIAL_PRICE' | 'DISCOUNT' | 'ACCESS'; +export type DiscountType = 'FIXED_AMOUNT' | 'FIXED_AMOUNT_RESERVATION' | 'PERCENTAGE' | 'NONE'; + +export interface DynamicDiscount { + discount: string; + discountType: DiscountType; + formattedMessage: {[key: string]: string}; +} diff --git a/frontend/projects/public/src/app/model/event.ts b/frontend/projects/public/src/app/model/event.ts new file mode 100644 index 0000000000..c604c26389 --- /dev/null +++ b/frontend/projects/public/src/app/model/event.ts @@ -0,0 +1,178 @@ +import {DatesWithOffset, DateValidity} from './date-validity'; +import {AnalyticsConfiguration} from './analytics-configuration'; +import {IconName, IconPrefix} from '@fortawesome/fontawesome-svg-core'; +import {OfflinePaymentConfiguration, PurchaseContext} from './purchase-context'; +import {EmbeddingConfiguration} from './embedding-configuration'; + +export interface TermsPrivacyLinksContainer { + privacyPolicyUrl?: string; + termsAndConditionsUrl?: string; +} + +export class Event implements DateValidity, PurchaseContext { + shortName: string; + title: { [lang: string]: string }; + format: EventFormat; + fileBlobId: string; + contentLanguages: Language[]; + websiteUrl: string; + location: string; + privacyPolicyUrl: string; + termsAndConditionsUrl: string; + mapUrl: string; + + organizationName: string; + organizationEmail: string; + + description: {[key: string]: string}; + vatIncluded: boolean; + vat: string; + free: boolean; + + // + bankAccount: string; + bankAccountOwner: string[]; + // + currency: string; + currencyDescriptor: CurrencyDescriptor; + + // date related + timeZone: string; + datesWithOffset: DatesWithOffset; + sameDay: boolean; + formattedBeginDate: {[key: string]: string}; // day, month, year + formattedBeginTime: {[key: string]: string}; // the hour/minute component + formattedEndDate: {[key: string]: string}; + formattedEndTime: {[key: string]: string}; + // + + // + invoicingConfiguration: InvoicingConfiguration; + // + captchaConfiguration: CaptchaConfiguration; + // + assignmentConfiguration: AssignmentConfiguration; + // + promotionsConfiguration: PromotionsConfiguration; + // + analyticsConfiguration: AnalyticsConfiguration; + + embeddingConfiguration: EmbeddingConfiguration; + + i18nOverride: {[lang: string]: {[key: string]: string}}; + + availableTicketsCount: number | null; + + customCss: string | null; + + canApplySubscriptions: boolean; + + offlinePaymentConfiguration: OfflinePaymentConfiguration = { + showOnlyBasicInstructions: false + }; +} + +export class InvoicingConfiguration { + userCanDownloadReceiptOrInvoice: boolean; + euVatCheckingEnabled: boolean; + invoiceAllowed: boolean; + onlyInvoice: boolean; + customerReferenceEnabled: boolean; + enabledItalyEInvoicing: boolean; + vatNumberStrictlyRequired: boolean; +} + +export class Language { + locale: string; + displayLanguage: string; +} + +export class PaymentProxyWithParameters { + paymentProxy: PaymentProxy; + parameters: {[key: string]: any}; +} + +export type EventFormat = 'IN_PERSON' | 'ONLINE' | 'HYBRID'; + +export type PaymentMethod = 'CREDIT_CARD' | 'PAYPAL' | 'IDEAL' | 'BANK_TRANSFER' | 'ON_SITE' + | 'APPLE_PAY' | 'BANCONTACT' | 'ING_HOME_PAY' | 'BELFIUS' | 'PRZELEWY_24' | 'KBC' | 'NONE'; +export type PaymentProxy = 'STRIPE' | 'ON_SITE' | 'OFFLINE' | 'PAYPAL' | 'MOLLIE' | 'SAFERPAY'; +export interface PaymentMethodDetails { + labelKey: string; + icon: [IconPrefix, IconName]; +} + +export const paymentMethodDetails: {[key in PaymentMethod]: PaymentMethodDetails} = { + 'CREDIT_CARD': { + labelKey: 'reservation-page.credit-card', + icon: ['fas', 'credit-card'] + }, + 'PAYPAL': { + labelKey: 'reservation-page.paypal', + icon: ['fab', 'paypal'] + }, + 'IDEAL': { + labelKey: 'reservation-page.payment-method.ideal', + icon: ['fab', 'ideal'] + }, + 'BANCONTACT': { + labelKey: 'reservation-page.payment-method.bancontact', + icon: ['fas', 'exchange-alt'] + }, + 'ING_HOME_PAY': { + labelKey: 'reservation-page.payment-method.ing-home-pay', + icon: ['fas', 'exchange-alt'] + }, + 'BELFIUS': { + labelKey: 'reservation-page.payment-method.belfius', + icon: ['fas', 'exchange-alt'] + }, + 'PRZELEWY_24': { + labelKey: 'reservation-page.payment-method.przelewy-24', + icon: ['fas', 'exchange-alt'] + }, + 'BANK_TRANSFER': { + labelKey: 'reservation-page.offline', + icon: ['fas', 'exchange-alt'] + }, + 'ON_SITE': { + labelKey: 'reservation-page.on-site', + icon: ['fas', 'money-bill'] + }, + 'APPLE_PAY': { + labelKey: 'reservation-page.payment-method.apple-pay', + icon: ['fab', 'apple-pay'] + }, + 'KBC': { + labelKey: 'reservation-page.payment-method.kbc', + icon: ['fas', 'money-check-alt'] + }, + 'NONE': { + labelKey: null, + icon: ['fas', 'exchange-alt'] + } +}; + +export class CaptchaConfiguration { + captchaForTicketSelection: boolean; + captchaForOfflinePaymentAndFree: boolean; + recaptchaApiKey: string; +} + +export class AssignmentConfiguration { + forceAssignment: boolean; + enableAttendeeAutocomplete: boolean; + enableTicketTransfer: boolean; +} + +export class PromotionsConfiguration { + hasAccessPromotions: boolean; + usePartnerCode: boolean; +} + +export interface CurrencyDescriptor { + code: string; + name: string; + symbol: string; + fractionDigits: number; +} diff --git a/frontend/projects/public/src/app/model/feedback.ts b/frontend/projects/public/src/app/model/feedback.ts new file mode 100644 index 0000000000..0c5c2218dc --- /dev/null +++ b/frontend/projects/public/src/app/model/feedback.ts @@ -0,0 +1,7 @@ +export type FeedbackType = 'SUCCESS' | 'ERROR' | 'INFO'; + +export interface FeedbackContent { + type?: FeedbackType; + active: boolean; + message?: string; +} diff --git a/frontend/projects/public/src/app/model/info.ts b/frontend/projects/public/src/app/model/info.ts new file mode 100644 index 0000000000..f328c2ab14 --- /dev/null +++ b/frontend/projects/public/src/app/model/info.ts @@ -0,0 +1,23 @@ +import {AnalyticsConfiguration} from './analytics-configuration'; +import {InvoicingConfiguration, TermsPrivacyLinksContainer} from './event'; + +export interface Info { + demoModeEnabled: boolean; + devModeEnabled: boolean; + prodModeEnabled: boolean; + analyticsConfiguration: AnalyticsConfiguration; + globalPrivacyPolicyUrl?: string; + globalTermsUrl?: string; + invoicingConfiguration?: InvoicingConfiguration; + announcementBannerContentHTML?: string; + walletConfiguration: WalletConfiguration; +} + +export interface WalletConfiguration { + gWalletEnabled: boolean; + passEnabled: boolean; +} + +export function globalTermsPrivacyLinks(info: Info): TermsPrivacyLinksContainer { + return { privacyPolicyUrl: info.globalPrivacyPolicyUrl, termsAndConditionsUrl: info.globalTermsUrl }; +} diff --git a/frontend/projects/public/src/app/model/items-by-category.ts b/frontend/projects/public/src/app/model/items-by-category.ts new file mode 100644 index 0000000000..4fb9da59d4 --- /dev/null +++ b/frontend/projects/public/src/app/model/items-by-category.ts @@ -0,0 +1,18 @@ +import {TicketCategory} from './ticket-category'; +import {AdditionalService} from './additional-service'; + +export class ItemsByCategory { + + ticketCategories: TicketCategory[]; + expiredCategories: TicketCategory[]; + additionalServices: AdditionalService[]; + + waitingList: boolean; + preSales: boolean; + ticketCategoriesForWaitingList: TicketCategoryForWaitingList[]; +} + +export class TicketCategoryForWaitingList { + id: number; + name: string; +} diff --git a/frontend/projects/public/src/app/model/localized-country.ts b/frontend/projects/public/src/app/model/localized-country.ts new file mode 100644 index 0000000000..5e63604081 --- /dev/null +++ b/frontend/projects/public/src/app/model/localized-country.ts @@ -0,0 +1,4 @@ +export class LocalizedCountry { + isoCode: string; + name: string; +} diff --git a/frontend/projects/public/src/app/model/overview-confirmation.ts b/frontend/projects/public/src/app/model/overview-confirmation.ts new file mode 100644 index 0000000000..5b71806488 --- /dev/null +++ b/frontend/projects/public/src/app/model/overview-confirmation.ts @@ -0,0 +1,4 @@ +export class OverviewConfirmation { + termAndConditionsAccepted: boolean; + privacyPolicyAccepted: boolean; +} diff --git a/frontend/projects/public/src/app/model/payment.ts b/frontend/projects/public/src/app/model/payment.ts new file mode 100644 index 0000000000..af82cb6a9c --- /dev/null +++ b/frontend/projects/public/src/app/model/payment.ts @@ -0,0 +1,4 @@ +export class TransactionInitializationToken { + clientSecret: string; + reservationStatusChanged: boolean; +} diff --git a/frontend/projects/public/src/app/model/purchase-context.ts b/frontend/projects/public/src/app/model/purchase-context.ts new file mode 100644 index 0000000000..d8a22b57b0 --- /dev/null +++ b/frontend/projects/public/src/app/model/purchase-context.ts @@ -0,0 +1,60 @@ +import {AnalyticsConfiguration} from './analytics-configuration'; +import {AssignmentConfiguration, CaptchaConfiguration, CurrencyDescriptor, InvoicingConfiguration, Language, TermsPrivacyLinksContainer} from './event'; +import {EmbeddingConfiguration} from './embedding-configuration'; + +export interface Localized { + contentLanguages: Language[]; +} + +export function filterAvailableLanguages(activeLanguages: Language[], purchaseContexts: Localized[]): Language[] { + if (purchaseContexts.length > 0) { + const languagesFromPc = purchaseContexts.map(pc => pc.contentLanguages.map(l => l.locale)).reduce((accumulator, current) => { + if (accumulator.length === 0) { + accumulator.push(...current); + return accumulator; + } else { + return accumulator.filter(l => current.some(l1 => l1 === l)); + } + }, []); + const filtered = activeLanguages.filter(al => languagesFromPc.some(l => l === al.locale)); + if (filtered.length > 0) { + return filtered; + } + } + return activeLanguages; +} + +export interface PurchaseContext extends PurchaseContextPriceDescriptor, Localized, TermsPrivacyLinksContainer { + title: { [lang: string]: string }; + invoicingConfiguration: InvoicingConfiguration; + assignmentConfiguration: AssignmentConfiguration; + analyticsConfiguration: AnalyticsConfiguration; + captchaConfiguration: CaptchaConfiguration; + offlinePaymentConfiguration: OfflinePaymentConfiguration; + + embeddingConfiguration: EmbeddingConfiguration; + + fileBlobId: string; + + bankAccount: string; + bankAccountOwner: string[]; + + organizationEmail: string; + + // + websiteUrl: string; + shortName: string; + + canApplySubscriptions: boolean; +} + +export interface PurchaseContextPriceDescriptor { + currencyDescriptor: CurrencyDescriptor; + vat: string; + currency: string; + vatIncluded: boolean; +} + +export interface OfflinePaymentConfiguration { + showOnlyBasicInstructions: boolean; +} diff --git a/frontend/projects/public/src/app/model/reservation-info.ts b/frontend/projects/public/src/app/model/reservation-info.ts new file mode 100644 index 0000000000..cc56332d96 --- /dev/null +++ b/frontend/projects/public/src/app/model/reservation-info.ts @@ -0,0 +1,146 @@ +import {Ticket} from './ticket'; +import {PaymentMethod, PaymentProxy, PaymentProxyWithParameters} from './event'; +import {TicketAccessType} from './ticket-category'; + +export class ReservationInfo { + id: string; + shortId: string; + firstName: string; + lastName: string; + email: string; + validity: number; + ticketsByCategory: TicketsByTicketCategory[]; + orderSummary: OrderSummary; + + // + status: ReservationStatus; + validatedBookingInformation: boolean; + // + formattedExpirationDate: {[key: string]: string}; + // + + // + invoiceNumber: string; + invoiceRequested: boolean; + invoiceOrReceiptDocumentPresent: boolean; + paid: boolean; + // + tokenAcquired: boolean; + paymentProxy: PaymentProxy; + // + addCompanyBillingDetails: boolean; + customerReference: string; + skipVatNr: boolean; + + billingAddress: string; + // + billingDetails: BillingDetails; + + // group related info + containsCategoriesLinkedToGroups: boolean; + // + + activePaymentMethods: {[key in PaymentMethod]?: PaymentProxyWithParameters}; + subscriptionInfos?: Array<ReservationSubscriptionInfo>; + metadata: ReservationMetadata; +} + +export interface ReservationMetadata { + hideContactData: boolean; + lockEmailEdit: boolean; + hideConfirmationButtons: boolean; + readyForConfirmation: boolean; + finalized: boolean; +} + +export class ReservationSubscriptionInfo { + id?: string; + pin?: string; + usageDetails?: SubscriptionUsageDetails; + owner?: SubscriptionOwner; + configuration?: SubscriptionConfiguration; +} + +export class SubscriptionOwner { + firstName: string; + lastName: string; + email: string; +} + +export interface SubscriptionConfiguration { + displayPin: boolean; +} + +export interface SubscriptionUsageDetails { + total: number | null; + used: number; + available: number | null; +} + +export class ReservationStatusInfo { + status: ReservationStatus; + validatedBookingInformation: boolean; +} + +export class TicketsByTicketCategory { + name: string; + ticketAccessType: TicketAccessType; + tickets: Ticket[]; +} + +export type SummaryType = 'TICKET' | 'PROMOTION_CODE' | 'DYNAMIC_DISCOUNT' | 'ADDITIONAL_SERVICE' | 'APPLIED_SUBSCRIPTION' | 'TAX_DETAIL'; + +export class OrderSummary { + summary: SummaryRow[]; + totalPrice: string; + free: boolean; + displayVat: boolean; + priceInCents: number; + descriptionForPayment: string; + totalVAT: string; + vatPercentage: string; +} + +export class SummaryRow { + name: string; + amount: number; + price: string; + subTotal: string; + type: SummaryType; + taxPercentage: string; +} + +export type ReservationStatus = 'PENDING' | 'IN_PAYMENT' | 'EXTERNAL_PROCESSING_PAYMENT' | + 'WAITING_EXTERNAL_CONFIRMATION' | 'OFFLINE_PAYMENT' | 'DEFERRED_OFFLINE_PAYMENT' | + 'OFFLINE_FINALIZING' | 'FINALIZING' | 'COMPLETE' | 'STUCK' | 'CANCELLED' | + 'CREDIT_NOTE_ISSUED' | 'NOT_FOUND'; + +export type ItalianEInvoicingReferenceType = 'ADDRESSEE_CODE' | 'PEC' | 'NONE'; + +export interface BillingDetails { + companyName: string; + addressLine1: string; + addressLine2: string; + zip: string; + city: string; + state: string; + country: string; + taxId: string; + invoicingAdditionalInfo: TicketReservationInvoicingAdditionalInfo; +} + +export interface TicketReservationInvoicingAdditionalInfo { + italianEInvoicing?: ItalianEInvoicing; +} + +export interface ItalianEInvoicing { + referenceType: ItalianEInvoicingReferenceType; + fiscalCode: string; + addresseeCode: string; + pec: string; + /** + * either addressee code, pec, or null + */ + reference: string; + splitPayment: boolean; +} diff --git a/frontend/projects/public/src/app/model/reservation-payment-result.ts b/frontend/projects/public/src/app/model/reservation-payment-result.ts new file mode 100644 index 0000000000..de64ed0519 --- /dev/null +++ b/frontend/projects/public/src/app/model/reservation-payment-result.ts @@ -0,0 +1,7 @@ +export class ReservationPaymentResult { + success: boolean; + failure: boolean; + redirect: boolean; + redirectUrl: string; + gatewayIdOrNull: string; +} diff --git a/frontend/projects/public/src/app/model/reservation-request.ts b/frontend/projects/public/src/app/model/reservation-request.ts new file mode 100644 index 0000000000..a1df5c591b --- /dev/null +++ b/frontend/projects/public/src/app/model/reservation-request.ts @@ -0,0 +1,17 @@ +export class ReservationRequest { + promoCode: string; + reservation: TicketReservation[]; + additionalService: SelectedAdditionalService[]; + captcha: string; +} + +export class TicketReservation { + ticketCategoryId: number; + amount: number; +} + +export class SelectedAdditionalService { + additionalServiceId: number; + amount: number; + quantity: number; +} diff --git a/frontend/projects/public/src/app/model/search-params.ts b/frontend/projects/public/src/app/model/search-params.ts new file mode 100644 index 0000000000..b648566961 --- /dev/null +++ b/frontend/projects/public/src/app/model/search-params.ts @@ -0,0 +1,46 @@ +import {Params} from '@angular/router'; +import {HttpParams} from '@angular/common/http'; + +export class SearchParams { + public constructor(private subscription: string, + private organizer: string, + public organizerSlug: string, + private tags: Array<string>) { + } + + public static fromQueryAndPathParams(params: Params, pathParams: Params): SearchParams { + return new SearchParams(params.subscription, params.organizer, pathParams.organizerSlug, params.tags); + } + + public static transformParams(params: Params, pathParams: Params): Params { + return SearchParams.fromQueryAndPathParams(params, pathParams).toParams(); + } + + public toHttpParams(): HttpParams { + return new HttpParams({ + fromObject: this.extractParams() + }); + } + + public toParams(): Params { + return this.extractParams(); + } + + private extractParams(): { [param: string]: string | ReadonlyArray<string> } { + const obj: { [param: string]: string | ReadonlyArray<string> } = {}; + if (this.subscription != null) { + obj.subscription = this.subscription; + } + if (this.organizer != null) { + obj.organizer = this.organizer; + } + if (this.tags != null) { + obj.tags = this.tags; + } + if (this.organizerSlug != null) { + obj.organizerSlug = this.organizerSlug; + } + return obj; + } + +} diff --git a/frontend/projects/public/src/app/model/subscription.ts b/frontend/projects/public/src/app/model/subscription.ts new file mode 100644 index 0000000000..ded4232f2f --- /dev/null +++ b/frontend/projects/public/src/app/model/subscription.ts @@ -0,0 +1,106 @@ +import {AnalyticsConfiguration} from './analytics-configuration'; +import {AssignmentConfiguration, CaptchaConfiguration, CurrencyDescriptor, InvoicingConfiguration, Language} from './event'; +import {Localized, OfflinePaymentConfiguration, PurchaseContext, PurchaseContextPriceDescriptor} from './purchase-context'; +import {DatesWithOffset} from './date-validity'; +import {EmbeddingConfiguration} from './embedding-configuration'; + +export interface SubscriptionSummaryData extends PurchaseContextPriceDescriptor, Localized { + salePeriod: DatesWithOffset; + formattedOnSaleFrom: { [key: string]: string }; + formattedOnSaleTo?: { [key: string]: string }; + formattedValidFrom?: { [key: string]: string }; + formattedValidTo?: { [key: string]: string }; + validityType: SubscriptionValidityType; + usageType: SubscriptionUsageType; + timeZone: string; + formattedPrice: string; + validityTimeUnit?: SubscriptionTimeUnit; + validityUnits?: number; + maxEntries?: number; + organizationEmail: string; + organizationName: string; +} + +export class BasicSubscriptionInfo implements SubscriptionSummaryData { + id: string; + fileBlobId: string; + title: { [lang: string]: string }; + description: { [lang: string]: string }; + + salePeriod: DatesWithOffset; + validityType: SubscriptionValidityType; + usageType: SubscriptionUsageType; + timeZone: string; + validityTimeUnit?: SubscriptionTimeUnit; + validityUnits?: number; + maxEntries?: number; + + organizationEmail: string; + organizationName: string; + + formattedPrice: string; + currency: string; + currencyDescriptor: CurrencyDescriptor; + vat: string; + vatIncluded: boolean; + + formattedOnSaleFrom: { [key: string]: string }; + formattedOnSaleTo?: { [key: string]: string }; + contentLanguages: Language[] = []; + +} + +export type SubscriptionValidityType = 'STANDARD' | 'CUSTOM' | 'NOT_SET'; +export type SubscriptionTimeUnit = 'DAYS' | 'MONTHS' | 'YEARS'; +export type SubscriptionUsageType = 'ONCE_PER_EVENT' | 'UNLIMITED'; + +export class SubscriptionInfo implements PurchaseContext, SubscriptionSummaryData { + id: string; + + invoicingConfiguration: InvoicingConfiguration; + assignmentConfiguration: AssignmentConfiguration; + analyticsConfiguration: AnalyticsConfiguration; + captchaConfiguration: CaptchaConfiguration; + embeddingConfiguration: EmbeddingConfiguration; + contentLanguages: Language[] = []; + termsAndConditionsUrl: string; + privacyPolicyUrl: string; + fileBlobId: string; + vat: string; + currencyDescriptor: CurrencyDescriptor; + currency: string; + vatIncluded: boolean; + title: { [lang: string]: string }; + description: { [lang: string]: string }; + formattedPrice: string; + numAvailable: number; + + // + bankAccount: string; + bankAccountOwner: string[]; + // + organizationEmail: string; + organizationName: string; + + canApplySubscriptions: boolean; + + // FIXME / CHECK: + websiteUrl: string; + shortName: string; + + salePeriod: DatesWithOffset; + formattedOnSaleFrom: { [key: string]: string }; + formattedOnSaleTo?: { [key: string]: string }; + timeZone: string; + validityType: SubscriptionValidityType; + usageType: SubscriptionUsageType; + validityTimeUnit?: SubscriptionTimeUnit; + validityUnits?: number; + formattedValidFrom?: { [key: string]: string }; + formattedValidTo?: { [key: string]: string }; + maxEntries?: number; + + offlinePaymentConfiguration: OfflinePaymentConfiguration = { + showOnlyBasicInstructions: false + }; +} diff --git a/frontend/projects/public/src/app/model/ticket-category.ts b/frontend/projects/public/src/app/model/ticket-category.ts new file mode 100644 index 0000000000..93fe47fcae --- /dev/null +++ b/frontend/projects/public/src/app/model/ticket-category.ts @@ -0,0 +1,31 @@ +export type TicketAccessType = 'INHERIT' | 'IN_PERSON' | 'ONLINE'; + +export interface TicketCategory { + id: number; + name: string; + ticketAccessType: TicketAccessType; + bounded: boolean; + maximumSaleableTickets: number; + description: {[key: string]: string}; + free: boolean; + formattedFinalPrice: string; + hasDiscount: boolean; + formattedDiscountedPrice: string; + + + // + expired: boolean; + saleInFuture: boolean; + formattedInception: {[key: string]: string}; + formattedExpiration: {[key: string]: string}; + // + + saleableAndLimitNotReached: boolean; + accessRestricted: boolean; + soldOutOrLimitReached: boolean; + + availableTickets: number | null; + + displayTaxInformation: boolean; +} + diff --git a/frontend/projects/public/src/app/model/ticket-info.ts b/frontend/projects/public/src/app/model/ticket-info.ts new file mode 100644 index 0000000000..b9e24a98da --- /dev/null +++ b/frontend/projects/public/src/app/model/ticket-info.ts @@ -0,0 +1,22 @@ +import {DatesWithOffset, DateValidity} from './date-validity'; + +export class TicketInfo implements DateValidity { + fullName: string; + email: string; + uuid: string; + + ticketCategoryName: string; + reservationFullName: string; + reservationId: string; + + deskPaymentRequired: boolean; + + + timeZone: string; + sameDay: boolean; + datesWithOffset: DatesWithOffset; + formattedBeginDate: {[key: string]: string}; // day, month, year + formattedBeginTime: {[key: string]: string}; // the hour/minute component + formattedEndDate: {[key: string]: string}; + formattedEndTime: {[key: string]: string}; +} diff --git a/frontend/projects/public/src/app/model/ticket.ts b/frontend/projects/public/src/app/model/ticket.ts new file mode 100644 index 0000000000..fbc3d6565f --- /dev/null +++ b/frontend/projects/public/src/app/model/ticket.ts @@ -0,0 +1,44 @@ +export class Ticket { + uuid: string; + firstName: string; + lastName: string; + email: string; + fullName: string; + userLanguage: string; + assigned: boolean; + locked: boolean; + acquired: boolean; + cancellationEnabled: boolean; + sendMailEnabled: boolean; + downloadEnabled: boolean; + ticketFieldConfigurationBeforeStandard: AdditionalField[]; + ticketFieldConfigurationAfterStandard: AdditionalField[]; + formattedOnlineCheckInDate: {[key: string]: string}; + onlineEventStarted: boolean; +} + +export class AdditionalField { + name: string; + value: string; + type: AdditionalFieldType; + required: boolean; + editable: boolean; + minLength: number; + maxLength: number; + restrictedValues: string[]; + fields: Field[]; + description: {[key: string]: Description}; +} + +export class Description { + label: string; + placeholder: string; + restrictedValuesDescription: {[key: string]: string}; +} + +export class Field { + fieldIndex: number; + fieldValue: string; +} + +export type AdditionalFieldType = 'input:text' | 'input:tel' | 'vat:eu' | 'textarea' | 'country' | 'select' | 'checkbox' | 'radio'; diff --git a/frontend/projects/public/src/app/model/user.ts b/frontend/projects/public/src/app/model/user.ts new file mode 100644 index 0000000000..4c5956c151 --- /dev/null +++ b/frontend/projects/public/src/app/model/user.ts @@ -0,0 +1,67 @@ +import {BillingDetails, ReservationStatus} from './reservation-info'; +import {PurchaseContextType} from '../shared/purchase-context.service'; + +export interface AuthenticationStatus { + enabled: boolean; + user?: User; +} + +export interface User { + firstName?: string; + lastName?: string; + emailAddress?: string; + profile?: UserProfile; + external?: boolean; +} + +export interface UserProfile { + billingDetails: BillingDetails; + additionalData: UserAdditionalData; +} + +export interface UserAdditionalData { + [key: string]: AdditionalInfoWithLabel; +} + +export interface AdditionalInfoWithLabel { + label: {[key: string]: string}; + values: string[]; +} + +export const ANONYMOUS: User = {}; + +export interface PurchaseContextWithReservation { + title: { [lang: string]: string }; + publicIdentifier: string; + type: PurchaseContextType; + formattedStartDate?: {[key: string]: string}; + formattedEndDate?: {[key: string]: string}; + sameDay: boolean; + reservations: Array<ReservationHeader>; +} + +export interface ReservationHeader { + id: string; + status: ReservationStatus; + formattedExpiresOn: {[key: string]: string}; + formattedConfirmedOn: {[key: string]: string}; + formattedCreatedOn: {[key: string]: string}; + invoiceNumber: string; + finalPrice: number; + currencyCode: string; + usedVatPercent: string; + vatStatus: string; + items: Array<PurchaseContextItem>; +} + +export interface ClientRedirect { + targetUrl?: string; + empty: boolean; +} + +export interface PurchaseContextItem { + id: string; + firstName: string; + lastName: string; + type: { [k: string]: string }; +} diff --git a/frontend/projects/public/src/app/model/validated-response.ts b/frontend/projects/public/src/app/model/validated-response.ts new file mode 100644 index 0000000000..04c88d0cdf --- /dev/null +++ b/frontend/projects/public/src/app/model/validated-response.ts @@ -0,0 +1,18 @@ +export class ValidatedResponse<T> { + success: boolean; + errorCount: number; + validationErrors: ErrorDescriptor[]; + value: T; + warnings: Array<WarningMessage>; +} + +export class ErrorDescriptor { + fieldName: string; + code: string; + arguments: {[key: string]: any}; +} + +export interface WarningMessage { + code: string; + params: Array<string>; +} diff --git a/frontend/projects/public/src/app/model/waiting-list-subscription-request.ts b/frontend/projects/public/src/app/model/waiting-list-subscription-request.ts new file mode 100644 index 0000000000..7fb4e34e7f --- /dev/null +++ b/frontend/projects/public/src/app/model/waiting-list-subscription-request.ts @@ -0,0 +1,9 @@ +export class WaitingListSubscriptionRequest { + firstName: string; + lastName: string; + email: string; + userLanguage: string; + termAndConditionsAccepted: string; + privacyPolicyAccepted: string; + selectedCategory: string; +} diff --git a/frontend/projects/public/src/app/my-orders/my-orders.component.html b/frontend/projects/public/src/app/my-orders/my-orders.component.html new file mode 100644 index 0000000000..3dff2935e1 --- /dev/null +++ b/frontend/projects/public/src/app/my-orders/my-orders.component.html @@ -0,0 +1,51 @@ +<div class="container mt-2"> + <div class="application-container p-md-4"> + <app-topbar [contentLanguages]="languages"></app-topbar> + <div class="page-header"> + <h1>{{ 'user.menu.my-orders' | translate }}</h1> + <small>{{ 'my-orders.description' | translate }}</small> + </div> + + <div *ngFor="let order of orders" class="mt-5"> + <h2>{{ localizedTitle(order) }} <ng-container *ngIf="order.formattedStartDate"> - <small class="text-muted">{{order.formattedStartDate | translateDescription }}</small></ng-container></h2> + <div class="row mt-4"> + <table class="table table-hover"> + <caption class="sr-only" translate="my-orders.for-pc.description" [translateParams]="{'0': 'purchase-context.'+order.type | translate, '1': localizedTitle(order)}"></caption> + <thead class="thead-dark"> + <tr> + <th class="text-center" translate="my-orders.id" style="width:10%"></th> + <th class="text-center" translate="my-orders.status" style="width:10%"></th> + <th class="text-center" translate="my-orders.confirmation-date" style="width: 20%"></th> + <th class="text-center" translate="my-orders.details"></th> + <th class="text-align-right" translate="my-orders.total-amount" style="width: 20%"></th> + </tr> + </thead> + <tbody> + <tr *ngFor="let reservation of order.reservations"> + <td class="text-center"> + <a [routerLink]="[ '/', order.type, order.publicIdentifier, 'reservation', reservation.id, 'book']" target="_blank" rel="noopener"> + {{ reservation.id | slice:0:8 | uppercase }} + </a> + </td> + <td [ngClass]="getTextClass(reservation)" class="text-center"><fa-icon [icon]="getStatusIcon(reservation)" a11yRole="presentation" [title]="getStatusDescription(reservation) | translate"></fa-icon></td> + <td class="text-center">{{reservation.formattedConfirmedOn | translateDescription }}</td> + <td> + <ul class="list-unstyled" *ngIf="reservation.items"> + <li *ngFor="let item of reservation.items"> + {{item.firstName}} {{item.lastName}} + <ng-container *ngIf="order.type === 'event'"><i class="text-muted">({{item.type | translateDescription}})</i></ng-container> + </li> + </ul> + </td> + <td class="text-align-right">{{getReservationCost(reservation)}}</td> + </tr> + </tbody> + </table> + </div> + </div> + <hr> + <div class="mt-4 text-center"> + <a class="btn btn-default" [routerLink]="['/']">{{ 'to-home' | translate }}</a> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/my-orders/my-orders.component.ts b/frontend/projects/public/src/app/my-orders/my-orders.component.ts new file mode 100644 index 0000000000..6ce4d43d4f --- /dev/null +++ b/frontend/projects/public/src/app/my-orders/my-orders.component.ts @@ -0,0 +1,65 @@ +import {Component, OnInit} from '@angular/core'; +import {UserService} from '../shared/user.service'; +import {PurchaseContextWithReservation, ReservationHeader} from '../model/user'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../shared/i18n.service'; +import {Language} from '../model/event'; +import {IconProp} from '@fortawesome/fontawesome-svg-core'; +import {getLocalizedContent} from '../shared/subscription.service'; + +@Component({ + selector: 'app-my-orders', + templateUrl: './my-orders.component.html' +}) +export class MyOrdersComponent implements OnInit { + + orders: Array<PurchaseContextWithReservation> = []; + languages: Language[]; + + constructor(private userService: UserService, + private translateService: TranslateService, + private i18nService: I18nService) { + } + + ngOnInit(): void { + this.userService.getOrders() + .subscribe(array => this.orders = array); + this.i18nService.getAvailableLanguages().subscribe(res => { + this.languages = res; + }); + this.i18nService.setPageTitle('user.menu.my-orders', null); + } + + localizedTitle(p: PurchaseContextWithReservation): string { + const lang = this.translateService.currentLang; + return getLocalizedContent(p.title, lang); + } + + getStatusIcon(reservation: ReservationHeader): IconProp { + if (reservation.status === 'COMPLETE') { + return ['fas', 'check']; + } + return ['far', 'clock']; + } + + getTextClass(reservation: ReservationHeader): string { + if (reservation.status === 'COMPLETE') { + return 'text-success'; + } + return 'text-warning'; + } + + getStatusDescription(reservation: ReservationHeader): string { + if (reservation.status === 'COMPLETE') { + return 'my-orders.status.complete'; + } + return 'my-orders.status.pending'; + } + + getReservationCost(reservation: ReservationHeader): string { + if (reservation.finalPrice > 0) { + return reservation.currencyCode + ' ' + reservation.finalPrice; + } + return this.translateService.instant('common.free'); + } +} diff --git a/frontend/projects/public/src/app/my-profile/my-profile-delete-warning.component.html b/frontend/projects/public/src/app/my-profile/my-profile-delete-warning.component.html new file mode 100644 index 0000000000..f67f368593 --- /dev/null +++ b/frontend/projects/public/src/app/my-profile/my-profile-delete-warning.component.html @@ -0,0 +1,26 @@ +<div class="modal-body text-center"> + <div class="text-danger mb-3"> + <fa-icon [icon]="['fas', 'exclamation-triangle']" [size]="'3x'" a11yRole="presentation"></fa-icon> + </div> + <h4 class="modal-title text-center"> + {{ 'my-profile.delete-profile' | translate}} + </h4> + <p class="mt-5 mb-5">{{ 'my-profile.delete-profile.description' | translate }}</p> + <form [formGroup]="form" (ngSubmit)="confirm()"> + <div class="form-check mb-4"> + <input class="form-check-input" id="acknowledge" type="checkbox" required name="acknowledge" formControlName="acknowledge" value="true" appInvalidFeedback [invalidFeedbackInLabel]="true"> + <label class="form-check-label" for="acknowledge"> + {{'my-profile.delete-profile.confirm' | translate}} + </label> + </div> + <hr/> + <div class="row"> + <div class="col-6"> + <button type="button" class="btn btn-light block-button" (click)="activeModal.dismiss()" translate="reservation-page.cancel"></button> + </div> + <div class="col-6"> + <button class="btn btn-danger block-button" translate="my-profile.delete-profile" [disabled]="!form.valid"></button> + </div> + </div> + </form> +</div> diff --git a/frontend/projects/public/src/app/my-profile/my-profile-delete-warning.component.ts b/frontend/projects/public/src/app/my-profile/my-profile-delete-warning.component.ts new file mode 100644 index 0000000000..c054c8eecc --- /dev/null +++ b/frontend/projects/public/src/app/my-profile/my-profile-delete-warning.component.ts @@ -0,0 +1,25 @@ +import {Component} from '@angular/core'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; + +@Component({ + selector: 'app-my-profile-delete-warning', + templateUrl: './my-profile-delete-warning.component.html' +}) +export class MyProfileDeleteWarningComponent { + + form: UntypedFormGroup; + + constructor(public activeModal: NgbActiveModal, builder: UntypedFormBuilder) { + this.form = builder.group({ + acknowledge: builder.control(false, Validators.required) + }); + } + + confirm(): void { + if (this.form.valid) { + console.log('closing'); + this.activeModal.close('ok'); + } + } +} diff --git a/frontend/projects/public/src/app/my-profile/my-profile.component.html b/frontend/projects/public/src/app/my-profile/my-profile.component.html new file mode 100644 index 0000000000..f8fd83033e --- /dev/null +++ b/frontend/projects/public/src/app/my-profile/my-profile.component.html @@ -0,0 +1,63 @@ +<div class="container mt-2" *ngIf="userForm && user"> + <form [formGroup]="userForm" (submit)="save()"> + <div class="application-container p-md-4"> + <app-topbar [contentLanguages]="languages"></app-topbar> + <div class="page-header"> + <h1>{{ 'user.menu.my-profile' | translate }}</h1> + <small>{{ 'my-profile.description' | translate }}</small> + </div> + + <div class="row g-2" [formGroup]="userForm"> + <div class="col-12 col-sm-6"> + <div class="form-group"> + <label class="form-label" for="first-name">{{'common.first-name'|translate}}{{' '}}*</label> + <input id="first-name" class="form-control" formControlName="firstName" aria-required="true" type="text" autocomplete="fname" [attr.maxlength]="255" appInvalidFeedback> + </div> + </div> + <div class="col-12 col-sm-6"> + <div class="form-group"> + <label class="form-label" for="last-name">{{'common.last-name'|translate}}{{' '}}*</label> + <input id="last-name" class="form-control" formControlName="lastName" aria-required="true" type="text" autocomplete="lname" [attr.maxlength]="255" appInvalidFeedback> + </div> + </div> + </div> + + <div *ngIf="invoicingConfiguration?.invoiceAllowed"> + <app-invoice-form [form]="userForm" [invoicingConfiguration]="invoicingConfiguration"></app-invoice-form> + </div> + + <div *ngIf="hasAdditionalData" formGroupName="additionalInfo"> + <div class="page-header"> + <h2 translate="my-profile.additional-info"></h2> + </div> + <div class="form-group" *ngFor="let item of additionalData | keyvalue"> + <label class="form-label" [attr.for]="item.key">{{item.value.label | translateDescription}}</label> + <input type="text" class="form-control" [attr.name]="'additional-'+item.key" [formControlName]="item.key" appInvalidFeedback> + </div> + </div> + + <hr> + <div class="mt-5"> + <div class="row d-flex justify-content-md-between"> + <div class="col-md-5 order-md-1 col-12 mb-2"> + <button class="block-button btn btn-success">{{ 'common.confirm' | translate }}</button> + </div> + <div class="col-md-5 order-md-0 col-12 mt-2 mt-md-0 mb-2"> + <a class="block-button btn btn-light" [routerLink]="['/']" translate="to-home"></a> + </div> + </div> + </div> + + <div class="border-top border-danger pt-5 delete-profile" *ngIf="user.external"> + <h4 class="text-danger">{{ 'my-profile.delete-profile' | translate }}</h4> + <p class="text-muted">{{ 'my-profile.delete-profile.description' | translate }}</p> + <div class="row d-flex justify-content-end"> + <div class="col-md-5 col-12 mt-2"> + <button type="button" class="btn block-button btn-danger" (click)="deleteProfile()">{{ 'my-profile.delete-profile' | translate }}</button> + </div> + </div> + </div> + + </div> + </form> +</div> diff --git a/frontend/projects/public/src/app/my-profile/my-profile.component.scss b/frontend/projects/public/src/app/my-profile/my-profile.component.scss new file mode 100644 index 0000000000..395f754f83 --- /dev/null +++ b/frontend/projects/public/src/app/my-profile/my-profile.component.scss @@ -0,0 +1,3 @@ +.delete-profile { + margin-top: 100px; +} diff --git a/frontend/projects/public/src/app/my-profile/my-profile.component.ts b/frontend/projects/public/src/app/my-profile/my-profile.component.ts new file mode 100644 index 0000000000..ab4247a90a --- /dev/null +++ b/frontend/projects/public/src/app/my-profile/my-profile.component.ts @@ -0,0 +1,131 @@ +import {Component, OnInit} from '@angular/core'; +import {UserService} from '../shared/user.service'; +import {User, UserAdditionalData} from '../model/user'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../shared/i18n.service'; +import {InvoicingConfiguration, Language} from '../model/event'; +import {zip} from 'rxjs'; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; +import {BookingComponent} from '../reservation/booking/booking.component'; +import {handleServerSideValidationError} from '../shared/validation-helper'; +import {ErrorDescriptor} from '../model/validated-response'; +import {InfoService} from '../shared/info.service'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {MyProfileDeleteWarningComponent} from './my-profile-delete-warning.component'; +import {FeedbackService} from '../shared/feedback/feedback.service'; +import {DELETE_ACCOUNT_CONFIRMATION, writeToSessionStorage} from '../shared/util'; + +@Component({ + selector: 'app-my-profile', + templateUrl: './my-profile.component.html', + styleUrls: ['./my-profile.component.scss'] +}) +export class MyProfileComponent implements OnInit { + + user?: User; + languages: Language[]; + userForm?: UntypedFormGroup; + invoicingConfiguration?: InvoicingConfiguration; + globalErrors: ErrorDescriptor[]; + additionalData?: UserAdditionalData; + + constructor(private userService: UserService, + private translateService: TranslateService, + private i18nService: I18nService, + private formBuilder: UntypedFormBuilder, + private infoService: InfoService, + private modalService: NgbModal, + private feedbackService: FeedbackService) { + this.userForm = this.formBuilder.group({ + firstName: this.formBuilder.control(null, Validators.required), + lastName: this.formBuilder.control(null, Validators.required), + addCompanyBillingDetails: this.formBuilder.control(false), + billingAddressCompany: this.formBuilder.control(null), + billingAddressLine1: this.formBuilder.control(null), + billingAddressLine2: this.formBuilder.control(null), + billingAddressZip: this.formBuilder.control(null), + billingAddressCity: this.formBuilder.control(null), + billingAddressState: this.formBuilder.control(null), + vatCountryCode: this.formBuilder.control(null), + skipVatNr: this.formBuilder.control(false), + vatNr: this.formBuilder.control(null), + italyEInvoicingFiscalCode: this.formBuilder.control(null), + italyEInvoicingReferenceType: this.formBuilder.control(null), + italyEInvoicingReferenceAddresseeCode: this.formBuilder.control(null), + italyEInvoicingReferencePEC: this.formBuilder.control(null), + italyEInvoicingSplitPayment: this.formBuilder.control(null), + additionalInfo: this.formBuilder.group({}) + }); + } + + ngOnInit(): void { + zip(this.userService.getUserIdentity(), this.i18nService.getAvailableLanguages(), this.infoService.getInfo()) + .subscribe(([user, languages, info]) => { + this.languages = languages; + this.i18nService.setPageTitle('user.menu.my-profile', null); + this.invoicingConfiguration = info.invoicingConfiguration; + let values: {[p: string]: any} = { + firstName: user.firstName, + lastName: user.lastName + }; + this.user = user; + const userBillingDetails = user.profile?.billingDetails; + if (userBillingDetails != null) { + values = { + ...values, + billingAddressCompany: userBillingDetails.companyName, + billingAddressLine1: userBillingDetails.addressLine1, + billingAddressLine2: userBillingDetails.addressLine2, + billingAddressZip: userBillingDetails.zip, + billingAddressCity: userBillingDetails.city, + billingAddressState: userBillingDetails.state, + vatCountryCode: userBillingDetails.country, + vatNr: userBillingDetails.taxId, + italyEInvoicingFiscalCode: BookingComponent.optionalGet(userBillingDetails, (i) => i.fiscalCode), + italyEInvoicingReferenceType: BookingComponent.optionalGet(userBillingDetails, (i) => i.referenceType), + italyEInvoicingReferenceAddresseeCode: BookingComponent.optionalGet(userBillingDetails, (i) => i.addresseeCode), + italyEInvoicingReferencePEC: BookingComponent.optionalGet(userBillingDetails, (i) => i.pec), + italyEInvoicingSplitPayment: BookingComponent.optionalGet(userBillingDetails, (i) => i.splitPayment) + }; + } + const additionalData = user.profile?.additionalData; + this.additionalData = additionalData; + if (additionalData != null) { + const additionalInfoGroup = this.userForm.get('additionalInfo') as UntypedFormGroup; + for (const additionalOptionKey of Object.keys(additionalData)) { + additionalInfoGroup.addControl(additionalOptionKey, this.formBuilder.control(additionalData[additionalOptionKey].values[0])); + } + } + this.userForm.patchValue(values); + }); + } + + + save(): void { + this.userService.updateUser(this.userForm.value) + .subscribe(res => { + if (res.success) { + this.feedbackService.showSuccess(this.translateService.instant('my-profile.update.success')); + this.user = res.value; + } else { + handleServerSideValidationError(res.validationErrors, this.userForm); + } + }, err => this.globalErrors = handleServerSideValidationError(err, this.userForm)); + } + + deleteProfile(): void { + const modalRef = this.modalService.open(MyProfileDeleteWarningComponent, {centered: true, backdrop: 'static'}); + modalRef.result.then(() => { + this.userService.deleteProfile().subscribe(response => { + if (!response.empty) { + writeToSessionStorage(DELETE_ACCOUNT_CONFIRMATION, 'y'); + window.location.href = response.targetUrl; + } + }, err => console.log('something went wrong', err)); + }); + } + + get hasAdditionalData(): boolean { + return this.additionalData != null && Object.keys(this.additionalData).length > 0; + } +} diff --git a/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-provider.ts b/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-provider.ts new file mode 100644 index 0000000000..920a9d7535 --- /dev/null +++ b/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-provider.ts @@ -0,0 +1,7 @@ +import {SimplePaymentProvider} from '../payment-provider'; + +export class MolliePaymentProvider extends SimplePaymentProvider { + override get paymentMethodDeferred(): boolean { + return false; + } +} diff --git a/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-proxy.component.html b/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-proxy.component.html new file mode 100644 index 0000000000..0d9eec1737 --- /dev/null +++ b/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-proxy.component.html @@ -0,0 +1,10 @@ +<div *ngIf="matchProxyAndMethod" class="row mt-2 mb-2"> + <div class="col-12 mb-3"> + <a href="https://mollie.com" target="_blank" rel="noreferrer noopener"> + <img src="assets/img/payment/mollie-badge.png" class="img-responsive"> + </a> + </div> + <div class="col-12"> + <div class="text-muted" translate="reservation-page.payment.mollie.description"></div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-proxy.component.ts new file mode 100644 index 0000000000..5af2605362 --- /dev/null +++ b/frontend/projects/public/src/app/payment/mollie-payment-proxy/mollie-payment-proxy.component.ts @@ -0,0 +1,43 @@ +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; + +import {PaymentProvider} from '../payment-provider'; +import {UntypedFormGroup} from '@angular/forms'; +import {MolliePaymentProvider} from './mollie-payment-provider'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; + +@Component({ + selector: 'app-mollie-payment-proxy', + templateUrl: './mollie-payment-proxy.component.html' +}) +export class MolliePaymentProxyComponent implements OnChanges { + + @Input() + method: PaymentMethod; + + @Input() + proxy: PaymentProxy; + + @Input() + parameters: {[key: string]: any}; + + @Input() + overviewForm: UntypedFormGroup; + + @Output() + paymentProvider: EventEmitter<PaymentProvider> = new EventEmitter<PaymentProvider>(); + + private compatibleMethods: PaymentMethod[] = ['CREDIT_CARD', 'IDEAL', 'APPLE_PAY', 'BANCONTACT', 'BELFIUS', 'ING_HOME_PAY', 'KBC', 'PRZELEWY_24']; + + constructor() { } + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes.method) { + this.paymentProvider.emit(new MolliePaymentProvider()); + } + } + + public get matchProxyAndMethod(): boolean { + return (this.compatibleMethods.includes(this.method)) && this.proxy === 'MOLLIE'; + } + +} diff --git a/frontend/projects/public/src/app/payment/offline-payment-proxy/offline-payment-proxy.component.html b/frontend/projects/public/src/app/payment/offline-payment-proxy/offline-payment-proxy.component.html new file mode 100644 index 0000000000..17df7c8715 --- /dev/null +++ b/frontend/projects/public/src/app/payment/offline-payment-proxy/offline-payment-proxy.component.html @@ -0,0 +1,9 @@ +<div *ngIf="matchProxyAndMethod" class="row mb-2 mt-2"> + <div class="col-12"> + <div class="text-muted" *ngIf="!deferred">{{'reservation-page.offline.description'|translate: {'0': parameters? parameters['delayForOfflinePayment'] : '-'} }}</div> + <div class="text-muted" *ngIf="deferred" translate="reservation-page.offline.deferred.description"></div> + </div> + <div class="col-12 mt-2" *ngIf="parameters && parameters.captchaRequestedForOffline && parameters.recaptchaApiKey"> + <app-recaptcha [apiKey]="parameters.recaptchaApiKey" (recaptchaResponse)="handleRecaptchaResponse($event)"></app-recaptcha> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/payment/offline-payment-proxy/offline-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/offline-payment-proxy/offline-payment-proxy.component.ts new file mode 100644 index 0000000000..2fefb12949 --- /dev/null +++ b/frontend/projects/public/src/app/payment/offline-payment-proxy/offline-payment-proxy.component.ts @@ -0,0 +1,46 @@ +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; +import {PaymentProvider, SimplePaymentProvider} from '../payment-provider'; +import {UntypedFormGroup} from '@angular/forms'; + +@Component({ + selector: 'app-offline-payment-proxy', + templateUrl: './offline-payment-proxy.component.html' +}) +export class OfflinePaymentProxyComponent implements OnChanges { + + @Input() + method: PaymentMethod; + + @Input() + proxy: PaymentProxy; + + @Input() + parameters: {[key: string]: any}; + + @Input() + overviewForm: UntypedFormGroup; + + @Output() + paymentProvider: EventEmitter<PaymentProvider> = new EventEmitter<PaymentProvider>(); + + constructor() { } + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes.method) { + this.paymentProvider.emit(new SimplePaymentProvider()); + } + } + + public get matchProxyAndMethod(): boolean { + return this.method === 'BANK_TRANSFER' && this.proxy === 'OFFLINE'; + } + + handleRecaptchaResponse(recaptchaValue: string) { + this.overviewForm.get('captcha').setValue(recaptchaValue); + } + + public get deferred(): boolean { + return this.parameters['deferred']; + } +} diff --git a/frontend/projects/public/src/app/payment/onsite-payment-proxy/onsite-payment-proxy.component.html b/frontend/projects/public/src/app/payment/onsite-payment-proxy/onsite-payment-proxy.component.html new file mode 100644 index 0000000000..80154e083b --- /dev/null +++ b/frontend/projects/public/src/app/payment/onsite-payment-proxy/onsite-payment-proxy.component.html @@ -0,0 +1,8 @@ +<div *ngIf="matchProxyAndMethod" class="row mb-2 mt-2"> + <div class="col-12"> + <div class="text-muted" translate="reservation-page.on-site.description"></div> + </div> + <div class="col-12 mt-2" *ngIf="parameters && parameters.captchaRequestedForOffline && parameters.recaptchaApiKey"> + <app-recaptcha [apiKey]="parameters.recaptchaApiKey" (recaptchaResponse)="handleRecaptchaResponse($event)"></app-recaptcha> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/payment/onsite-payment-proxy/onsite-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/onsite-payment-proxy/onsite-payment-proxy.component.ts new file mode 100644 index 0000000000..6436170649 --- /dev/null +++ b/frontend/projects/public/src/app/payment/onsite-payment-proxy/onsite-payment-proxy.component.ts @@ -0,0 +1,43 @@ +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; +import {PaymentProvider, SimplePaymentProvider} from '../payment-provider'; +import {UntypedFormGroup} from '@angular/forms'; + +@Component({ + selector: 'app-onsite-payment-proxy', + templateUrl: './onsite-payment-proxy.component.html' +}) +export class OnsitePaymentProxyComponent implements OnChanges { + + @Input() + method: PaymentMethod; + + @Input() + proxy: PaymentProxy; + + @Input() + parameters: {[key: string]: any}; + + @Input() + overviewForm: UntypedFormGroup; + + @Output() + paymentProvider: EventEmitter<PaymentProvider> = new EventEmitter<PaymentProvider>(); + + constructor() { } + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes.method) { + this.paymentProvider.emit(new SimplePaymentProvider()); + } + } + + public get matchProxyAndMethod(): boolean { + return this.method === 'ON_SITE' && this.proxy === 'ON_SITE'; + } + + handleRecaptchaResponse(recaptchaValue: string) { + this.overviewForm.get('captcha').setValue(recaptchaValue); + } + +} diff --git a/frontend/projects/public/src/app/payment/payment-provider.ts b/frontend/projects/public/src/app/payment/payment-provider.ts new file mode 100644 index 0000000000..bd7c592971 --- /dev/null +++ b/frontend/projects/public/src/app/payment/payment-provider.ts @@ -0,0 +1,30 @@ +import {EMPTY, Observable, of} from 'rxjs'; + +export interface PaymentProvider { + readonly paymentMethodDeferred: boolean; + pay(): Observable<PaymentResult>; + statusNotifications(): Observable<PaymentStatusNotification>; +} + +export class PaymentResult { + constructor(public success: boolean, public gatewayToken: string, public reason: string = null, public reservationChanged = false) {} +} + +export class PaymentStatusNotification { + constructor(public delayed: boolean, public indeterminate: boolean) {} +} + +export class SimplePaymentProvider implements PaymentProvider { + + pay(): Observable<PaymentResult> { + return of(new PaymentResult(true, null)); + } + + get paymentMethodDeferred(): boolean { + return true; + } + + statusNotifications(): Observable<PaymentStatusNotification> { + return EMPTY; + } +} diff --git a/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-provider.ts b/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-provider.ts new file mode 100644 index 0000000000..9ffe8f4b20 --- /dev/null +++ b/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-provider.ts @@ -0,0 +1,7 @@ +import {SimplePaymentProvider} from '../payment-provider'; + +export class PayPalPaymentProvider extends SimplePaymentProvider { + override get paymentMethodDeferred(): boolean { + return false; + } +} diff --git a/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-proxy.component.html b/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-proxy.component.html new file mode 100644 index 0000000000..b601ea770a --- /dev/null +++ b/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-proxy.component.html @@ -0,0 +1,13 @@ +<div *ngIf="matchProxyAndMethod" class="row mb-2 mt-2"> + <div class="col-12"> + <div class="text-muted"> + <div translate="reservation-page.paypal.description"></div> + </div> + </div> + + <div class="col-12" *ngIf="reservation && reservation.tokenAcquired"> + <div class="alert alert-success mb-2 mt-2"> + <h3><fa-icon [icon]="['fas', 'check']" a11yRole="presentation"></fa-icon>{{' '}}<span translate="reservation-page.paypal.confirm"></span></h3> + </div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-proxy.component.ts new file mode 100644 index 0000000000..9dbe81bda7 --- /dev/null +++ b/frontend/projects/public/src/app/payment/paypal-payment-proxy/paypal-payment-proxy.component.ts @@ -0,0 +1,36 @@ +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; +import {PaymentProvider} from '../payment-provider'; +import {ReservationInfo} from '../../model/reservation-info'; +import {PayPalPaymentProvider} from './paypal-payment-provider'; + +@Component({ + selector: 'app-paypal-payment-proxy', + templateUrl: './paypal-payment-proxy.component.html', +}) +export class PaypalPaymentProxyComponent implements OnChanges { + + @Input() + method: PaymentMethod; + + @Input() + proxy: PaymentProxy; + + @Input() + reservation: ReservationInfo; + + @Output() + paymentProvider: EventEmitter<PaymentProvider> = new EventEmitter<PaymentProvider>(); + + constructor() { } + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes.method) { + this.paymentProvider.emit(new PayPalPaymentProvider()); + } + } + + public get matchProxyAndMethod(): boolean { + return this.proxy === 'PAYPAL'; + } +} diff --git a/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-provider.ts b/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-provider.ts new file mode 100644 index 0000000000..62cf1e5bea --- /dev/null +++ b/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-provider.ts @@ -0,0 +1,7 @@ +import {SimplePaymentProvider} from '../payment-provider'; + +export class SaferpayPaymentProvider extends SimplePaymentProvider { + override get paymentMethodDeferred(): boolean { + return false; + } +} diff --git a/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-proxy.component.html b/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-proxy.component.html new file mode 100644 index 0000000000..c3641d55e4 --- /dev/null +++ b/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-proxy.component.html @@ -0,0 +1,8 @@ +<div *ngIf="matchProxyAndMethod" class="row mt-3 mb-2"> + <div class="col-12 mb-3"> + <h4 class="text-muted">Saferpay by SIX</h4> + </div> + <div class="col-12"> + <div class="text-muted" translate="reservation-page.payment.saferpay.description"></div> + </div> +</div> diff --git a/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-proxy.component.ts new file mode 100644 index 0000000000..6624f7a20b --- /dev/null +++ b/frontend/projects/public/src/app/payment/saferpay-payment-proxy/saferpay-payment-proxy.component.ts @@ -0,0 +1,34 @@ +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; +import {ReservationInfo} from '../../model/reservation-info'; +import {PaymentProvider} from '../payment-provider'; +import {SaferpayPaymentProvider} from './saferpay-payment-provider'; + +@Component({ + selector: 'app-saferpay-payment-proxy', + templateUrl: './saferpay-payment-proxy.component.html' +}) +export class SaferpayPaymentProxyComponent implements OnChanges { + @Input() + method: PaymentMethod; + + @Input() + proxy: PaymentProxy; + + @Input() + reservation: ReservationInfo; + + @Output() + paymentProvider: EventEmitter<PaymentProvider> = new EventEmitter<PaymentProvider>(); + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes.method) { + this.paymentProvider.emit(new SaferpayPaymentProvider()); + } + } + + public get matchProxyAndMethod(): boolean { + return this.proxy === 'SAFERPAY'; + } + +} diff --git a/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-providers.ts b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-providers.ts new file mode 100644 index 0000000000..e154a257cf --- /dev/null +++ b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-providers.ts @@ -0,0 +1,173 @@ +import {EMPTY, Observable, Subject, Subscriber} from 'rxjs'; +import {PaymentProvider, PaymentResult, PaymentStatusNotification} from '../payment-provider'; +import {TranslateService} from '@ngx-translate/core'; +import {ReservationInfo} from '../../model/reservation-info'; +import {ReservationService} from '../../shared/reservation.service'; +import {PurchaseContext} from '../../model/purchase-context'; + +// global variable defined by stripe when the scripts are loaded +declare const StripeCheckout: any; +// + +export const STRIPE_CHECKOUT_ID_SCRIPT = 'stripe-payment-proxy-checkout'; + +export class StripeCheckoutPaymentProvider implements PaymentProvider { + + constructor( + private translate: TranslateService, + private parameters: { [key: string]: any }, + private reservation: ReservationInfo, + private purchaseContext: PurchaseContext) { + } + + get paymentMethodDeferred(): boolean { + return false; + } + + pay(): Observable<PaymentResult> { + const obs = new Observable<PaymentResult>(subscriber => { + this.loadScript(subscriber); + }); + return obs; + } + + loadScript(subscriber: Subscriber<PaymentResult>) { + if (!document.getElementById(STRIPE_CHECKOUT_ID_SCRIPT)) { + const scriptElem = document.createElement('script'); + scriptElem.id = STRIPE_CHECKOUT_ID_SCRIPT; + scriptElem.src = 'https://checkout.stripe.com/checkout.js'; + scriptElem.async = true; + scriptElem.defer = true; + scriptElem.addEventListener('load', () => { + this.configureAndOpen(subscriber); + }); + document.body.appendChild(scriptElem); + } else if (!window['StripeCheckout']) { + document.getElementById(STRIPE_CHECKOUT_ID_SCRIPT).addEventListener('load', () => { + this.configureAndOpen(subscriber); + }); + } else { + this.configureAndOpen(subscriber); + } + } + + configureAndOpen(subscriber: Subscriber<PaymentResult>) { + let tokenSubmitted = false; + const stripeHandler = StripeCheckout.configure({ + key: this.parameters['stripe_p_key'], + locale: this.translate.currentLang, + token: (token) => { + tokenSubmitted = true; + subscriber.next(new PaymentResult(true, token.id)); + }, + closed: () => { + if (!tokenSubmitted) { + subscriber.next(new PaymentResult(false, null)); + } + } + }); + stripeHandler.open({ + name: `${this.reservation.firstName} ${this.reservation.lastName}`, + description: this.reservation.orderSummary.descriptionForPayment, + zipCode: false, + allowRememberMe: false, + amount: this.reservation.orderSummary.priceInCents, + currency: this.purchaseContext.currency, + email: this.reservation.email + }); + } + + statusNotifications(): Observable<PaymentStatusNotification> { + return EMPTY; + } +} + +export class StripePaymentV3 implements PaymentProvider { + + private notificationSubject = new Subject<PaymentStatusNotification>(); + + constructor( + private reservationService: ReservationService, + private reservation: ReservationInfo, + private stripeHandler: any, + private card: any + ) { + } + + get paymentMethodDeferred(): boolean { + return false; + } + + pay(): Observable<PaymentResult> { + + const obs = new Observable<PaymentResult>(subscriber => { + + this.reservationService.initPayment(this.reservation.id).subscribe(res => { + + if (res.reservationStatusChanged || res.clientSecret == null) { + subscriber.next(new PaymentResult(false, null, null, res.reservationStatusChanged)); + return; + } + + const clientSecret = res.clientSecret; + let billingAddress = null; + if (this.reservation.billingDetails.addressLine1 != null) { + billingAddress = { + line1: this.reservation.billingDetails.addressLine1, + postal_code: this.reservation.billingDetails.zip, + country: StripePaymentV3.toCountryISOCode(this.reservation.billingDetails.country).toLowerCase() + }; + } + const paymentData = { + payment_method_data: { + billing_details: { + name: `${this.reservation.firstName} ${this.reservation.lastName}`, + email: this.reservation.email, + address: billingAddress + } + } + }; + + this.stripeHandler.handleCardPayment(clientSecret, this.card, paymentData).then(cardPaymentResult => { + let retryCount = 0; + let handleCheck: number; + const checkIfPaid = () => { + console.log('checking reservation status...'); + retryCount++; + if (retryCount % 10 === 0) { + this.notificationSubject.next(new PaymentStatusNotification(true, retryCount > 120)); + } + this.reservationService.getPaymentStatus(this.reservation.id).subscribe(status => { + if (cardPaymentResult.error || status.success) { + window.clearInterval(handleCheck); + } + if (status.success) { + subscriber.next(new PaymentResult(true, status.gatewayIdOrNull)); + } else if (cardPaymentResult.error) { + subscriber.error(new PaymentResult(false, null, cardPaymentResult.error.message)); + } else if (status.failure) { + subscriber.error(new PaymentResult(false, null)); + } + }, err => console.log('got error while calling getStatus(). Retrying in 1s', err)); + }; + handleCheck = window.setInterval(checkIfPaid, 1000); + }, err => { + subscriber.error(err); + }); + }, err => { + subscriber.error(err); + }); + }); + return obs; + } + + statusNotifications(): Observable<PaymentStatusNotification> { + return this.notificationSubject.asObservable(); + } + + private static toCountryISOCode(country: string): string { + // The form contains EU-VAT prefix for Greece (EL). + // Here we need the country ISO Code instead + return country === 'EL' ? 'GR' : country; + } +} diff --git a/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.html b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.html new file mode 100644 index 0000000000..4021e83545 --- /dev/null +++ b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.html @@ -0,0 +1,40 @@ +<div *ngIf="matchProxyAndMethod" class="row mb-3"> + <div *ngIf="useSCA" class="col-12 mb-3"> + <div class="mt-2 row mb-3"> + <div class="col-12"> + <div class="form-group"> + <label for="card-name" class="control-label" translate="reservation-page.cardholder.name"></label> + <input type="text" data-stripe="name" required class="form-control" id="card-name" autocomplete="name" value=""> + </div> + </div> + </div> + <div class="row mt-3"> + <div class="col-12"> + <div class="form-group" id="card-element-container"> + <label for="card-element" class="control-label" translate="reservation-page.card.details"></label> + <div> + <div id="card-element"></div> + </div> + <div class="mt-2 text-danger payment-errors hide" role="alert" id="card-errors"> + <strong id="error-message"></strong> + </div> + </div> + </div> + </div> + </div> + <div class="col-md-3 col-12 mb-2 mb-md-0"> + <div class="text-muted"><a href="https://stripe.com/" target="_blank" rel="noreferrer noopener"> + <!-- inlined powered by stripe svg icon , https://stripe.com/en-ch/newsroom/brand-assets--> + <svg xmlns="http://www.w3.org/2000/svg" width="119px" height="26px"> + <path fill-rule="evenodd" opacity="0.349" fill="rgb(66, 71, 112)" d="M113.000,26.000 L6.000,26.000 C2.686,26.000 -0.000,23.314 -0.000,20.000 L-0.000,6.000 C-0.000,2.686 2.686,-0.000 6.000,-0.000 L113.000,-0.000 C116.314,-0.000 119.000,2.686 119.000,6.000 L119.000,20.000 C119.000,23.314 116.314,26.000 113.000,26.000 ZM118.000,6.000 C118.000,3.239 115.761,1.000 113.000,1.000 L6.000,1.000 C3.239,1.000 1.000,3.239 1.000,6.000 L1.000,20.000 C1.000,22.761 3.239,25.000 6.000,25.000 L113.000,25.000 C115.761,25.000 118.000,22.761 118.000,20.000 L118.000,6.000 Z"/> + <path fill-rule="evenodd" opacity="0.502" fill="rgb(66, 71, 112)" d="M60.700,18.437 L59.395,18.437 L60.405,15.943 L58.395,10.871 L59.774,10.871 L61.037,14.323 L62.310,10.871 L63.689,10.871 L60.700,18.437 ZM55.690,16.259 C55.238,16.259 54.774,16.091 54.354,15.764 L54.354,16.133 L53.007,16.133 L53.007,8.566 L54.354,8.566 L54.354,11.229 C54.774,10.913 55.238,10.745 55.690,10.745 C57.100,10.745 58.068,11.881 58.068,13.502 C58.068,15.122 57.100,16.259 55.690,16.259 ZM55.406,11.902 C55.038,11.902 54.669,12.060 54.354,12.376 L54.354,14.628 C54.669,14.943 55.038,15.101 55.406,15.101 C56.164,15.101 56.690,14.449 56.690,13.502 C56.690,12.555 56.164,11.902 55.406,11.902 ZM47.554,15.764 C47.144,16.091 46.681,16.259 46.218,16.259 C44.818,16.259 43.840,15.122 43.840,13.502 C43.840,11.881 44.818,10.745 46.218,10.745 C46.681,10.745 47.144,10.913 47.554,11.229 L47.554,8.566 L48.912,8.566 L48.912,16.133 L47.554,16.133 L47.554,15.764 ZM47.554,12.376 C47.249,12.060 46.881,11.902 46.513,11.902 C45.744,11.902 45.218,12.555 45.218,13.502 C45.218,14.449 45.744,15.101 46.513,15.101 C46.881,15.101 47.249,14.943 47.554,14.628 L47.554,12.376 ZM39.535,13.870 C39.619,14.670 40.251,15.217 41.134,15.217 C41.619,15.217 42.155,15.038 42.702,14.722 L42.702,15.849 C42.103,16.122 41.503,16.259 40.913,16.259 C39.324,16.259 38.209,15.101 38.209,13.460 C38.209,11.871 39.303,10.745 40.808,10.745 C42.187,10.745 43.123,11.829 43.123,13.375 C43.123,13.523 43.123,13.691 43.102,13.870 L39.535,13.870 ZM40.756,11.786 C40.103,11.786 39.598,12.271 39.535,12.997 L41.829,12.997 C41.787,12.281 41.356,11.786 40.756,11.786 ZM35.988,12.618 L35.988,16.133 L34.641,16.133 L34.641,10.871 L35.988,10.871 L35.988,11.397 C36.367,10.976 36.830,10.745 37.282,10.745 C37.430,10.745 37.577,10.755 37.724,10.797 L37.724,11.997 C37.577,11.955 37.409,11.934 37.251,11.934 C36.809,11.934 36.335,12.176 35.988,12.618 ZM29.979,13.870 C30.063,14.670 30.694,15.217 31.578,15.217 C32.062,15.217 32.599,15.038 33.146,14.722 L33.146,15.849 C32.546,16.122 31.946,16.259 31.357,16.259 C29.768,16.259 28.653,15.101 28.653,13.460 C28.653,11.871 29.747,10.745 31.252,10.745 C32.630,10.745 33.567,11.829 33.567,13.375 C33.567,13.523 33.567,13.691 33.546,13.870 L29.979,13.870 ZM31.199,11.786 C30.547,11.786 30.042,12.271 29.979,12.997 L32.273,12.997 C32.231,12.281 31.799,11.786 31.199,11.786 ZM25.274,16.133 L24.200,12.555 L23.137,16.133 L21.927,16.133 L20.117,10.871 L21.464,10.871 L22.527,14.449 L23.590,10.871 L24.810,10.871 L25.873,14.449 L26.936,10.871 L28.283,10.871 L26.484,16.133 L25.274,16.133 ZM17.043,16.259 C15.454,16.259 14.328,15.112 14.328,13.502 C14.328,11.881 15.454,10.745 17.043,10.745 C18.632,10.745 19.748,11.881 19.748,13.502 C19.748,15.112 18.632,16.259 17.043,16.259 ZM17.043,11.871 C16.254,11.871 15.707,12.534 15.707,13.502 C15.707,14.470 16.254,15.133 17.043,15.133 C17.822,15.133 18.369,14.470 18.369,13.502 C18.369,12.534 17.822,11.871 17.043,11.871 ZM11.128,13.533 L9.918,13.533 L9.918,16.133 L8.571,16.133 L8.571,8.892 L11.128,8.892 C12.602,8.892 13.654,9.850 13.654,11.218 C13.654,12.586 12.602,13.533 11.128,13.533 ZM10.939,9.987 L9.918,9.987 L9.918,12.439 L10.939,12.439 C11.718,12.439 12.265,11.944 12.265,11.218 C12.265,10.482 11.718,9.987 10.939,9.987 Z"/> + <path fill-rule="evenodd" opacity="0.502" fill="rgb(66, 71, 112)" d="M111.116,14.051 L105.557,14.051 C105.684,15.382 106.659,15.774 107.766,15.774 C108.893,15.774 109.781,15.536 110.555,15.146 L110.555,17.433 C109.784,17.861 108.765,18.169 107.408,18.169 C104.642,18.169 102.704,16.437 102.704,13.013 C102.704,10.121 104.348,7.825 107.049,7.825 C109.746,7.825 111.154,10.120 111.154,13.028 C111.154,13.303 111.129,13.898 111.116,14.051 ZM107.031,10.140 C106.321,10.140 105.532,10.676 105.532,11.955 L108.468,11.955 C108.468,10.677 107.728,10.140 107.031,10.140 ZM98.108,18.169 C97.114,18.169 96.507,17.750 96.099,17.451 L96.093,20.664 L93.254,21.268 L93.253,8.014 L95.753,8.014 L95.901,8.715 C96.293,8.349 97.012,7.825 98.125,7.825 C100.119,7.825 101.997,9.621 101.997,12.927 C101.997,16.535 100.139,18.169 98.108,18.169 ZM97.446,10.340 C96.795,10.340 96.386,10.578 96.090,10.903 L96.107,15.122 C96.383,15.421 96.780,15.661 97.446,15.661 C98.496,15.661 99.200,14.518 99.200,12.989 C99.200,11.504 98.485,10.340 97.446,10.340 ZM89.149,8.014 L91.999,8.014 L91.999,17.966 L89.149,17.966 L89.149,8.014 ZM89.149,4.836 L91.999,4.230 L91.999,6.543 L89.149,7.149 L89.149,4.836 ZM86.110,11.219 L86.110,17.966 L83.272,17.966 L83.272,8.014 L85.727,8.014 L85.905,8.853 C86.570,7.631 87.897,7.879 88.275,8.015 L88.275,10.625 C87.914,10.508 86.781,10.338 86.110,11.219 ZM80.024,14.475 C80.024,16.148 81.816,15.627 82.179,15.482 L82.179,17.793 C81.801,18.001 81.115,18.169 80.187,18.169 C78.502,18.169 77.237,16.928 77.237,15.247 L77.250,6.138 L80.022,5.548 L80.024,8.014 L82.180,8.014 L82.180,10.435 L80.024,10.435 L80.024,14.475 ZM76.485,14.959 C76.485,17.003 74.858,18.169 72.497,18.169 C71.518,18.169 70.448,17.979 69.392,17.525 L69.392,14.814 C70.345,15.332 71.559,15.721 72.500,15.721 C73.133,15.721 73.589,15.551 73.589,15.026 C73.589,13.671 69.273,14.181 69.273,11.038 C69.273,9.028 70.808,7.825 73.111,7.825 C74.052,7.825 74.992,7.969 75.933,8.344 L75.933,11.019 C75.069,10.552 73.972,10.288 73.109,10.288 C72.514,10.288 72.144,10.460 72.144,10.903 C72.144,12.181 76.485,11.573 76.485,14.959 Z"/> + </svg> + </a> + </div> + </div> + <div class="col-md-9 col-12"> + <div class="text-muted" translate="reservation-page.credit-card.description"></div> + </div> + +</div> diff --git a/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.scss b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.scss new file mode 100644 index 0000000000..0ed7407b71 --- /dev/null +++ b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.scss @@ -0,0 +1,37 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; + +.StripeElement { + box-sizing: border-box; + + height: $input-height; + + padding: 0.5rem $input-padding-x; + + border: $input-border-width solid $input-border-color; + border-radius: $input-border-radius; + background-color: $input-bg; + + box-shadow: $input-box-shadow; + -webkit-transition: box-shadow 150ms ease; + transition: box-shadow 150ms ease; +} + +.StripeElement--focus { + background-color: $input-bg; + border-color: $input-focus-border-color; + color: $input-focus-color; + outline: 0; + -webkit-box-shadow: $input-focus-box-shadow; + box-shadow: $input-focus-box-shadow; +} + +.StripeElement--invalid { + border-color: $form-feedback-invalid-color; + -webkit-box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-invalid-color, .25); + box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-invalid-color, .25); +} + +.StripeElement--webkit-autofill { + background-color: #fefde5 !important; +} diff --git a/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.ts new file mode 100644 index 0000000000..bc320d6d7c --- /dev/null +++ b/frontend/projects/public/src/app/payment/stripe-payment-proxy/stripe-payment-proxy.component.ts @@ -0,0 +1,146 @@ +import {Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges} from '@angular/core'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; +import {ReservationInfo} from '../../model/reservation-info'; +import {TranslateService} from '@ngx-translate/core'; +import {PaymentProvider} from '../payment-provider'; +import {ReservationService} from '../../shared/reservation.service'; +import {STRIPE_CHECKOUT_ID_SCRIPT, StripeCheckoutPaymentProvider, StripePaymentV3} from './stripe-payment-providers'; +import {removeDOMNode} from '../../shared/event.service'; +import {PurchaseContext} from '../../model/purchase-context'; + +// global variable defined by stripe when the scripts are loaded +declare const Stripe: any; +// + +const STRIPE_V3_ID_SCRIPT = 'stripe-payment-v3-script'; + +@Component({ + selector: 'app-stripe-payment-proxy', + templateUrl: './stripe-payment-proxy.component.html', + styleUrls: ['./stripe-payment-proxy.component.scss'] +}) +export class StripePaymentProxyComponent implements OnChanges, OnDestroy { + + @Input() + purchaseContext: PurchaseContext; + + @Input() + reservation: ReservationInfo; + + @Input() + method: PaymentMethod; + + @Input() + proxy: PaymentProxy; + + @Input() + parameters: { [key: string]: any }; + + @Output() + paymentProvider: EventEmitter<PaymentProvider> = new EventEmitter<PaymentProvider>(); + + constructor( + private translate: TranslateService, + private reservationService: ReservationService) { } + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes.method) { + if (this.parameters['enableSCA']) { + this.loadSCA(); + } else { + this.loadNonSCA(); + } + } else { + this.unloadAll(); + } + } + + get useSCA(): boolean { + return this.parameters && this.parameters['enableSCA']; + } + + ngOnDestroy(): void { + this.unloadAll(); + } + + private unloadAll(): void { + const checkoutScript = document.getElementById(STRIPE_CHECKOUT_ID_SCRIPT); + if (checkoutScript && removeDOMNode(checkoutScript)) { + delete window['StripeCheckout']; // TODO: check + } + const stripeV3Script = document.getElementById(STRIPE_V3_ID_SCRIPT); + if (stripeV3Script && removeDOMNode(stripeV3Script)) { + delete window['Stripe']; // TODO: check + } + } + + private loadNonSCA(): void { + this.paymentProvider.emit(new StripeCheckoutPaymentProvider(this.translate, this.parameters, this.reservation, this.purchaseContext)); + } + + public get matchProxyAndMethod(): boolean { + return this.proxy === 'STRIPE'; + } + + + // + private loadSCA(): void { + if (!document.getElementById(STRIPE_V3_ID_SCRIPT)) { + const scriptElem = document.createElement('script'); + scriptElem.id = STRIPE_V3_ID_SCRIPT; + scriptElem.src = 'https://js.stripe.com/v3/'; + scriptElem.async = true; + scriptElem.defer = true; + scriptElem.addEventListener('load', () => { + this.configureSCA(); + }); + document.body.appendChild(scriptElem); + } else if (!window['Stripe']) { + document.getElementById(STRIPE_V3_ID_SCRIPT).addEventListener('load', () => { + this.configureSCA(); + }); + } else { + this.configureSCA(); + } + } + + private configureSCA() { + const options = {}; + + if (this.parameters['stripeConnectedAccount']) { + options['stripeAccount'] = this.parameters['stripeConnectedAccount']; + } + + const stripeHandler = Stripe(this.parameters['stripe_p_key'], options); + const card = stripeHandler.elements({ locale: this.translate.currentLang }).create('card', { + style: { + base: { + color: '#495057', + lineHeight: '18px', + fontFamily: `"Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`, + fontSmoothing: 'antialiased', + fontSize: '1rem', + '::placeholder': { + color: '#aab7c4' + } + }, + invalid: { + color: '#a94442', + iconColor: '#a94442' + } + } + }); + + card.addEventListener('change', (ev) => { + // TODO: show errors & co + if (ev.complete) { + this.paymentProvider.emit(new StripePaymentV3(this.reservationService, this.reservation, stripeHandler, card)); // enable payment + } else { + this.paymentProvider.emit(null); // -> disable submit buttons by providing an empty payment provider + } + }); + card.mount('#card-element'); + } +} diff --git a/frontend/projects/public/src/app/poll/display-poll/display-poll.component.html b/frontend/projects/public/src/app/poll/display-poll/display-poll.component.html new file mode 100644 index 0000000000..f73e85f521 --- /dev/null +++ b/frontend/projects/public/src/app/poll/display-poll/display-poll.component.html @@ -0,0 +1,37 @@ +<div *ngIf="poll"> + <h2 class="text-center">{{poll.poll.title[translate.currentLang]}}</h2> + <p class="text-center">{{poll.poll.description[translate.currentLang]}}</p> + <ng-container *ngIf="pollSubmittedWithSuccess"> + <div class="alert alert-success text-center mt-4" translate="poll.thank-you-for-the-vote"></div> + <div class="row"> + <div class="col-md-6 offset-md-3 col-12 mt-4"> + <a class="btn btn-default block-button" routerLink="../" queryParamsHandling="merge" translate="common.back"></a> + </div> + </div> + </ng-container> + <form [formGroup]="pollForm" (submit)="submitChoice()" *ngIf="!pollSubmittedWithSuccess"> + <div class="row mt-2"> + <div class="col-12 col-md-6 offset-md-3"> + <ul class="list-group list-group-flush"> + <li class="list-group-item" *ngFor="let option of poll.options" + [ngClass]="{'bg-success text-dark bg-opacity-25': pollForm.value.optionId === option.id}"> + <div class="form-check"> + <input type="radio" formControlName="optionId" [value]="option.id" class="form-check-input" id="option-{{option.id}}"> + <label class="form-check-label" for="option-{{option.id}}"> + <span class="poll-text"> + <span class="poll-title">{{option.title[translate.currentLang]}}</span> + <span class="poll-description" *ngIf="option.description[translate.currentLang]">{{option.description[translate.currentLang]}}</span> + </span> + </label> + </div> + </li> + </ul> + </div> + </div> + <div class="row"> + <div class="col-md-6 offset-md-3 col-12 mt-4"> + <button class="btn btn-success block-button" type="submit" translate="poll.vote-button"></button> + </div> + </div> + </form> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/poll/display-poll/display-poll.component.scss b/frontend/projects/public/src/app/poll/display-poll/display-poll.component.scss new file mode 100644 index 0000000000..b77129173b --- /dev/null +++ b/frontend/projects/public/src/app/poll/display-poll/display-poll.component.scss @@ -0,0 +1,29 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/variables"; +.poll-text { + display: inline-block; + vertical-align: top; + padding-left: 1rem; +} + +.poll-title, .poll-description { + display: block; +} + +.poll-title { + font-weight: bold; +} + +.poll-question { + border-bottom:1px solid rgba(0, 0, 0, 0.125);; +} + +.poll-question.last { + border-bottom: none; +} + +.selected { + --bs-bg-opacity: .5; + background-color: $success; +} diff --git a/frontend/projects/public/src/app/poll/display-poll/display-poll.component.ts b/frontend/projects/public/src/app/poll/display-poll/display-poll.component.ts new file mode 100644 index 0000000000..9333775ce8 --- /dev/null +++ b/frontend/projects/public/src/app/poll/display-poll/display-poll.component.ts @@ -0,0 +1,58 @@ +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {PollService} from '../shared/poll.service'; +import {combineLatest} from 'rxjs'; +import {PollWithOptions} from '../model/poll-with-options'; +import {TranslateService} from '@ngx-translate/core'; +import {FormGroup, UntypedFormBuilder} from '@angular/forms'; +import {PollVoteForm} from '../model/poll-vote-form'; + +@Component({ + selector: 'app-display-poll', + templateUrl: './display-poll.component.html', + styleUrls: ['./display-poll.component.scss'] +}) +export class DisplayPollComponent implements OnInit { + + + eventShortName: string; + pollId: number; + pin: string; + poll: PollWithOptions; + + pollForm: FormGroup<PollVoteForm>; + pollSubmittedWithSuccess: boolean; + + constructor( + private route: ActivatedRoute, + public translate: TranslateService, + private pollService: PollService, + private fb: UntypedFormBuilder) { } + + ngOnInit(): void { + + this.pollForm = this.fb.group({optionId: null}); + + combineLatest([this.route.parent.params, this.route.params, this.route.queryParams]).subscribe(([parentParams, params, query]) => { + this.eventShortName = parentParams['eventShortName']; + this.pollId = parseInt(params['pollId']); + this.pin = query['pin']; + this.loadPoll() + }); + } + + + loadPoll() { + this.pollService.getPoll(this.eventShortName, this.pollId, this.pin).subscribe(res => { + if (res.success) { + this.poll = res.value; + } + }) + } + + submitChoice() { + this.pollService.registerAnswer(this.eventShortName, this.pollId, {pin: this.pin, optionId: this.pollForm.value.optionId}).subscribe(res => { + this.pollSubmittedWithSuccess = res.success && res.value; + }) + } +} diff --git a/frontend/projects/public/src/app/poll/model/poll-option.ts b/frontend/projects/public/src/app/poll/model/poll-option.ts new file mode 100644 index 0000000000..c6de240fe0 --- /dev/null +++ b/frontend/projects/public/src/app/poll/model/poll-option.ts @@ -0,0 +1,6 @@ +export class PollOption { + id: number; + pollId: number; + title: {[key: string]: string}; + description: {[key: string]: string}; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/poll/model/poll-vote-form.ts b/frontend/projects/public/src/app/poll/model/poll-vote-form.ts new file mode 100644 index 0000000000..715827c3e4 --- /dev/null +++ b/frontend/projects/public/src/app/poll/model/poll-vote-form.ts @@ -0,0 +1,10 @@ +import {FormControl} from '@angular/forms'; + +export interface PollVoteForm { + optionId: FormControl<number>; +} + +export interface PollVotePayload { + pin: string; + optionId: number; +} diff --git a/frontend/projects/public/src/app/poll/model/poll-with-options.ts b/frontend/projects/public/src/app/poll/model/poll-with-options.ts new file mode 100644 index 0000000000..5b1a025869 --- /dev/null +++ b/frontend/projects/public/src/app/poll/model/poll-with-options.ts @@ -0,0 +1,7 @@ +import {PollOption} from './poll-option'; +import {Poll} from './poll'; + +export class PollWithOptions { + poll: Poll; + options: PollOption[]; +} diff --git a/frontend/projects/public/src/app/poll/model/poll.ts b/frontend/projects/public/src/app/poll/model/poll.ts new file mode 100644 index 0000000000..eee19eaa84 --- /dev/null +++ b/frontend/projects/public/src/app/poll/model/poll.ts @@ -0,0 +1,12 @@ +export class Poll { + id: number; + status: PollStatus; + title: {[key: string]: string }; + description: {[key: string]: string }; + allowedTags: string[]; + order: number; + eventId: number; + organizationId: number; +} + +export type PollStatus = 'DRAFT' | 'OPEN' | 'CLOSED' \ No newline at end of file diff --git a/frontend/projects/public/src/app/poll/poll-routing.module.ts b/frontend/projects/public/src/app/poll/poll-routing.module.ts new file mode 100644 index 0000000000..92959172ac --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll-routing.module.ts @@ -0,0 +1,21 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; + +import {PollComponent} from './poll.component'; +import {PollService} from './shared/poll.service'; +import {DisplayPollComponent} from './display-poll/display-poll.component'; +import {PollSelectionComponent} from './poll-selection/poll-selection.component'; + +const routes: Routes = [ + { path: '', component: PollComponent, children: [ + {path: '', component: PollSelectionComponent }, + {path: ':pollId', component: DisplayPollComponent } + ]} +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [PollService] +}) +export class PollRoutingModule { } diff --git a/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.html b/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.html new file mode 100644 index 0000000000..5a9897cb92 --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.html @@ -0,0 +1,34 @@ +<ng-container *ngIf="ready"> + <div *ngIf="!polls" class="mt-4"> + <form [formGroup]="pinForm" (submit)="confirmPin()"> + <div class="row"> + <div class="col-12 col-md-6 offset-md-3"> + <h2 translate="poll.title"></h2> + </div> + </div> + <div class="row mt-4"> + <div class="col-12 col-md-6 offset-md-3"> + <div class="form-group"> + <label class="form-label" for="pin" translate="poll.enter-your-pin"></label> + <input id="pin" formControlName="pin" class="form-control" appInvalidFeedback> + </div> + </div> + </div> + <div class="row mt-5"> + <div class="col-md-6 offset-md-3 col-12 mt-4"> + <button class="btn btn-success block-button" type="submit" translate="show-event.continue"></button> + </div> + </div> + </form> + </div> + + <div *ngIf="polls && polls.length > 0" class="mt-4"> + <h2 translate="poll.select"></h2> + <div class="list-group poll-list mt-4"> + <a class="list-group-item list-group-item-action" *ngFor="let poll of polls; let idx = index" [routerLink]="[poll.id]" queryParamsHandling="merge"> + <h3>{{idx + 1}}. {{poll.title[translate.currentLang]}}</h3> + <p class="mb-0" *ngIf="poll.description[translate.currentLang]">{{poll.description[translate.currentLang]}}</p> + </a> + </div> + </div> +</ng-container> diff --git a/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.scss b/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.scss new file mode 100644 index 0000000000..e2c8dbe0f2 --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.scss @@ -0,0 +1,12 @@ +.poll-list .list-group-item { + border-left: 1px solid rgba(0, 0, 0, 0.125);; + border-right: 1px solid rgba(0, 0, 0, 0.125);; +} + +.poll-list .list-group-item:first-child { + border-top: 1px solid rgba(0, 0, 0, 0.125);; +} + +.poll-list .list-group-item:last-child { + border-bottom:1px solid rgba(0, 0, 0, 0.125);; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.ts b/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.ts new file mode 100644 index 0000000000..38a1437e76 --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll-selection/poll-selection.component.ts @@ -0,0 +1,67 @@ +import {Component, OnInit} from '@angular/core'; +import {FormControl, FormGroup, UntypedFormBuilder} from '@angular/forms'; +import {Poll} from '../model/poll'; +import {ActivatedRoute, Router} from '@angular/router'; +import {PollService} from '../shared/poll.service'; +import {TranslateService} from '@ngx-translate/core'; +import {combineLatest} from 'rxjs'; +import {handleServerSideValidationError} from '../../shared/validation-helper'; +import {ErrorDescriptor} from '../../model/validated-response'; + +@Component({ + selector: 'app-poll-selection', + templateUrl: './poll-selection.component.html', + styleUrls: ['./poll-selection.component.scss'] +}) +export class PollSelectionComponent implements OnInit { + + pinForm: FormGroup<{ + pin: FormControl<string> + }>; + polls: Poll[]; + eventShortName: string; + globalErrors: ErrorDescriptor[]; + ready: boolean; + + constructor( + private route: ActivatedRoute, + private router: Router, + private pollService: PollService, + public translate: TranslateService, + private fb: UntypedFormBuilder) { } + + ngOnInit(): void { + this.pinForm = this.fb.group({pin: null}); + combineLatest([this.route.params, this.route.queryParams]).subscribe(([params, query]) => { + this.eventShortName = params['eventShortName']; + if (query['pin']) { + this.loadPolls(this.eventShortName, query['pin']); + } else { + this.ready = true; + } + }); + } + + private loadPolls(eventShortName: string, pin: string) { + this.ready = false; + this.pollService.getAllPolls(eventShortName, pin).subscribe(res => { + if (res.success) { + this.router.navigate([], {queryParamsHandling: 'merge', queryParams: {pin: pin}}); + this.polls = res.value; + if (this.polls.length === 1) { + this.router.navigate([this.polls[0].id], {relativeTo: this.route, queryParamsHandling: 'merge', queryParams: {pin: pin}}); + } + } + this.ready = true; + }, (err) => { + this.globalErrors = handleServerSideValidationError(err, this.pinForm); + this.ready = true; + }); + } + + confirmPin() { + const pin = this.pinForm.value.pin; + this.loadPolls(this.eventShortName, pin); + } + +} diff --git a/frontend/projects/public/src/app/poll/poll.component.html b/frontend/projects/public/src/app/poll/poll.component.html new file mode 100644 index 0000000000..ec2f790e01 --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll.component.html @@ -0,0 +1,9 @@ +<div class="container"> + <div *ngIf="event" class="mb-2 application-container p-md-4 mt-2"> + <header><app-purchase-context-header [purchaseContext]="event" type="event"></app-purchase-context-header></header> + <main> + <hr> + <router-outlet></router-outlet> + </main> + </div> +</div> diff --git a/frontend/projects/public/src/app/poll/poll.component.scss b/frontend/projects/public/src/app/poll/poll.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/public/src/app/poll/poll.component.ts b/frontend/projects/public/src/app/poll/poll.component.ts new file mode 100644 index 0000000000..6cf374dece --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll.component.ts @@ -0,0 +1,33 @@ +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {EventService} from '../shared/event.service'; +import {TranslateService} from '@ngx-translate/core'; +import {Event} from '../model/event'; +import {I18nService} from '../shared/i18n.service'; + +@Component({ + selector: 'app-live-poll', + templateUrl: './poll.component.html', + styleUrls: ['./poll.component.scss'] +}) +export class PollComponent implements OnInit { + + event: Event + + constructor( + private route: ActivatedRoute, + private eventService: EventService, + public translate: TranslateService, + public i18nService: I18nService) { } + + ngOnInit(): void { + + this.route.params.subscribe(params => { + const eventShortName = params['eventShortName']; + this.eventService.getEvent(eventShortName).subscribe(ev => { + this.i18nService.setPageTitle('poll.page.title', ev); + this.event = ev; + }); + }); + } +} diff --git a/frontend/projects/public/src/app/poll/poll.module.ts b/frontend/projects/public/src/app/poll/poll.module.ts new file mode 100644 index 0000000000..58aac2acd4 --- /dev/null +++ b/frontend/projects/public/src/app/poll/poll.module.ts @@ -0,0 +1,24 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {PollRoutingModule} from './poll-routing.module'; +import {PollComponent} from './poll.component'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {DisplayPollComponent} from './display-poll/display-poll.component'; +import {TranslateModule} from '@ngx-translate/core'; +import {SharedModule} from '../shared/shared.module'; +import {PollSelectionComponent} from './poll-selection/poll-selection.component'; + + +@NgModule({ + declarations: [PollComponent, DisplayPollComponent, PollSelectionComponent], + imports: [ + TranslateModule.forChild(), + CommonModule, + PollRoutingModule, + FormsModule, + ReactiveFormsModule, + SharedModule + ] +}) +export class PollModule { } diff --git a/frontend/projects/public/src/app/poll/shared/poll.service.ts b/frontend/projects/public/src/app/poll/shared/poll.service.ts new file mode 100644 index 0000000000..972eca9688 --- /dev/null +++ b/frontend/projects/public/src/app/poll/shared/poll.service.ts @@ -0,0 +1,26 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {Poll} from '../model/poll'; +import {PollWithOptions} from '../model/poll-with-options'; +import {PollVotePayload} from '../model/poll-vote-form'; +import {ValidatedResponse} from '../../model/validated-response'; +import {HttpClient} from '@angular/common/http'; + +@Injectable() +export class PollService { + + constructor(private http: HttpClient) { } + + + getAllPolls(eventName: string, pin: string): Observable<ValidatedResponse<Poll[]>> { + return this.http.get<ValidatedResponse<Poll[]>>(`/api/v2/public/event/${eventName}/poll`, {params: {pin: pin}}); + } + + getPoll(eventName: string, pollId: number, pin: string): Observable<ValidatedResponse<PollWithOptions>> { + return this.http.get<ValidatedResponse<PollWithOptions>>(`/api/v2/public/event/${eventName}/poll/${pollId}`, {params: {pin: pin}}); + } + + registerAnswer(eventName: string, pollId: number, pollForm: PollVotePayload): Observable<ValidatedResponse<boolean>> { + return this.http.post<ValidatedResponse<boolean>>(`/api/v2/public/event/${eventName}/poll/${pollId}/answer`, pollForm); + } +} diff --git a/frontend/projects/public/src/app/price-tag/price-tag.component.html b/frontend/projects/public/src/app/price-tag/price-tag.component.html new file mode 100644 index 0000000000..a04afc93d1 --- /dev/null +++ b/frontend/projects/public/src/app/price-tag/price-tag.component.html @@ -0,0 +1,11 @@ +<div class="d-inline-flex" [class.flex-md-column]="!singleLineLayout"> + <div [class.text-align-right]="!singleLineLayout"> + <span *ngIf="currencyDescriptor" [ngClass]="{'me-2': !displayTextInline && displayCurrencySymbol, 'me-1': !displayTextInline && !displayCurrencySymbol}">{{currencyDescriptor.symbol}}</span><ng-container *ngIf="displayTextInline">{{' '}}</ng-container> + <span *ngIf="displayDiscountedPrice" class="text-align-right"><del>{{formattedPrice}}</del>{{' '}}<mark>{{discountedPrice}}</mark></span> + <span *ngIf="!displayDiscountedPrice" class="text-align-right">{{formattedPrice}}</span> + </div> + <div class="ms-2" *ngIf="singleLineLayout"> </div> + <div *ngIf="showTaxes"> + <small class="text-align-right text-muted align-self-end ms-2 ms-md-0"><i>{{(purchaseContext.vatIncluded ? 'show-event.incVat' : 'show-event.excVat') | translate:{'0': purchaseContext.vat, '1': ('common.vat' | translate)} }}</i></small> + </div> + </div> diff --git a/frontend/projects/public/src/app/price-tag/price-tag.component.ts b/frontend/projects/public/src/app/price-tag/price-tag.component.ts new file mode 100644 index 0000000000..1bb9442490 --- /dev/null +++ b/frontend/projects/public/src/app/price-tag/price-tag.component.ts @@ -0,0 +1,58 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {CurrencyDescriptor} from '../model/event'; +import {PurchaseContextPriceDescriptor} from '../model/purchase-context'; + +@Component({ + selector: 'app-price-tag', + templateUrl: './price-tag.component.html' +}) +export class PriceTagComponent implements OnInit { + @Input() + purchaseContext: PurchaseContextPriceDescriptor; + @Input() + formattedPrice: string; + @Input() + discountedPrice: string; + @Input() + showTaxDetails: boolean; + @Input() + displayTextInline: boolean; + @Input() + singleLineLayout: boolean; + @Input() + showDiscount: boolean; + + displayCurrencySymbol: boolean; + currencyDescriptor: CurrencyDescriptor; + + ngOnInit(): void { + this.currencyDescriptor = this.purchaseContext.currencyDescriptor; + this.displayCurrencySymbol = this.currencyDescriptor && this.currencyDescriptor.symbol !== this.currencyDescriptor.code; + } + + get displayDiscountedPrice(): boolean { + return this.showDiscount && this.discountedPrice != null && this.discountedPrice !== this.formattedPrice; + } + + get showTaxes(): boolean { + return this.showTaxDetails && (Number(this.purchaseContext.vat) || 0) > 0.0; + } + + removeRedundantPrecision(input: string): string { + let substringEnd = input.lastIndexOf('.'); + if (substringEnd > -1) { + const fraction = input.substring(substringEnd + 1); + let additionalChars = 1; + for (let i = 0; i < fraction.length; i++) { + if (fraction.charAt(i) !== '0') { + additionalChars++; + } + } + if (additionalChars > 1) { + substringEnd += additionalChars; + } + return input.substring(0, substringEnd); + } + return input; + } +} diff --git a/frontend/projects/public/src/app/purchase-context-container/purchase-context-container.component.html b/frontend/projects/public/src/app/purchase-context-container/purchase-context-container.component.html new file mode 100644 index 0000000000..5fbb9eccfc --- /dev/null +++ b/frontend/projects/public/src/app/purchase-context-container/purchase-context-container.component.html @@ -0,0 +1,8 @@ +<div class="container mt-2"> + <div class="application-container p-md-4"> + <ng-content></ng-content> + <div class="mt-5" *ngIf="displayFooterLinks"> + <app-footer-links [marginClass]="'mb-2'" [linksContainer]="purchaseContext"></app-footer-links> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/purchase-context-container/purchase-context-container.component.ts b/frontend/projects/public/src/app/purchase-context-container/purchase-context-container.component.ts new file mode 100644 index 0000000000..646e6e4b35 --- /dev/null +++ b/frontend/projects/public/src/app/purchase-context-container/purchase-context-container.component.ts @@ -0,0 +1,14 @@ +import {Component, Input} from '@angular/core'; +import {PurchaseContext} from '../model/purchase-context'; + +@Component({ + selector: 'app-purchase-context-container', + templateUrl: './purchase-context-container.component.html' +}) +export class PurchaseContextContainerComponent { + @Input() + purchaseContext: PurchaseContext; + + @Input() + displayFooterLinks = true; +} diff --git a/frontend/projects/public/src/app/recaptcha/recaptcha.component.ts b/frontend/projects/public/src/app/recaptcha/recaptcha.component.ts new file mode 100644 index 0000000000..f48d10248a --- /dev/null +++ b/frontend/projects/public/src/app/recaptcha/recaptcha.component.ts @@ -0,0 +1,101 @@ +import {AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, ViewChild} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; +import {Subscription} from 'rxjs'; + + +declare const grecaptcha: any; + +let idCallback = 0; + +@Component({ + selector: 'app-recaptcha', + template: '<div #targetElement class="c-container"></div>' +}) +export class RecaptchaComponent implements OnDestroy, AfterViewInit { + + @Input() + apiKey: string; + + private langSub: Subscription; + + @ViewChild('targetElement', { static: true }) + private targetElement: ElementRef<HTMLDivElement>; + + @Output() + recaptchaResponse: EventEmitter<string> = new EventEmitter<string>(); + + + private widgetId: any = null; + + constructor(private translate: TranslateService) { } + + ngAfterViewInit() { + if (!document.getElementById('recaptcha-api-script')) { + const scriptElem = document.createElement('script'); + + idCallback++; + + const callBackName = `onloadRecaptchaCallback${idCallback}`; + + scriptElem.src = `https://www.google.com/recaptcha/api.js?onload=${callBackName}&render=explicit`; + scriptElem.id = 'recaptcha-api-script'; + scriptElem.async = true; + scriptElem.defer = true; + + window[callBackName] = () => { + this.enableRecaptcha(); + }; + document.body.appendChild(scriptElem); + } else if (!window['grecaptcha']) { + // wait until it's available + const waiter = () => { + if (window['grecaptcha']) { + this.enableRecaptcha(); + } else { + setTimeout(waiter, 100); + } + }; + setTimeout(waiter, 100); + } else { + this.enableRecaptcha(); + } + + this.langSub = this.translate.onLangChange.subscribe(change => { + this.enableRecaptcha(); + }); + } + + ngOnDestroy() { + if (this.langSub) { + this.langSub.unsubscribe(); + } + if (window['grecaptcha'] && this.widgetId !== null) { + grecaptcha.reset(this.widgetId); + } + } + + enableRecaptcha() { + + // delete container if present + const range = document.createRange(); + range.selectNodeContents(this.targetElement.nativeElement); + range.deleteContents(); + // + + const container = document.createElement('div'); + this.targetElement.nativeElement.appendChild(container); + // + + + if (window['grecaptcha']) { + this.widgetId = grecaptcha.render(container, { + sitekey: this.apiKey, + hl: this.translate.currentLang, + callback: (res) => { + this.recaptchaResponse.emit(res); + } + }); + } + } + +} diff --git a/frontend/projects/public/src/app/remove-event-css.guard.ts b/frontend/projects/public/src/app/remove-event-css.guard.ts new file mode 100644 index 0000000000..229cf5c9de --- /dev/null +++ b/frontend/projects/public/src/app/remove-event-css.guard.ts @@ -0,0 +1,15 @@ +import {Injectable} from '@angular/core'; +import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree} from '@angular/router'; +import {Observable} from 'rxjs'; +import {removeAllCustomEventCss} from './shared/custom-css-helper'; + +@Injectable({ + providedIn: 'root' +}) +export class RemoveEventCssGuard implements CanActivate { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { + removeAllCustomEventCss(); + return true; + } + +} diff --git a/frontend/projects/public/src/app/reservation/animated-dots/animated-dots.component.scss b/frontend/projects/public/src/app/reservation/animated-dots/animated-dots.component.scss new file mode 100644 index 0000000000..e04a3bb59e --- /dev/null +++ b/frontend/projects/public/src/app/reservation/animated-dots/animated-dots.component.scss @@ -0,0 +1,17 @@ +/* source: https://stackoverflow.com/a/24349758 */ + +@keyframes dots-1 { from { opacity: 0; } 25% { opacity: 1; } } +@keyframes dots-2 { from { opacity: 0; } 50% { opacity: 1; } } +@keyframes dots-3 { from { opacity: 0; } 75% { opacity: 1; } } + +.dots span { + animation: dots-1 1s infinite steps(1); +} + +.dots span:first-child + span { + animation-name: dots-2; +} + +.dots span:first-child + span + span { + animation-name: dots-3; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/animated-dots/animated-dots.component.ts b/frontend/projects/public/src/app/reservation/animated-dots/animated-dots.component.ts new file mode 100644 index 0000000000..30f62d5cbe --- /dev/null +++ b/frontend/projects/public/src/app/reservation/animated-dots/animated-dots.component.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-animated-dots', + template: '<span class="dots"><span>.</span><span>.</span><span>.</span></span>', + styleUrls: ['./animated-dots.component.scss'] +}) +export class AnimatedDotsComponent { + +} diff --git a/frontend/projects/public/src/app/reservation/booking/booking.component.html b/frontend/projects/public/src/app/reservation/booking/booking.component.html new file mode 100644 index 0000000000..1311c54160 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/booking/booking.component.html @@ -0,0 +1,143 @@ +<app-reservation> + +<div *ngIf="reservationInfo && purchaseContext"> + + <app-stepper [currentStep]="2" [free]="reservationInfo.orderSummary.free"></app-stepper> + + <app-countdown *ngIf="!expired" [validity]="reservationInfo.validity" (expired)="handleExpired($event)"></app-countdown> + + <form [formGroup]="contactAndTicketsForm" (submit)="submitForm()"> + + <ng-container *ngIf="showContactData"> + + <div class="page-header"> + <h2 translate="reservation-page.your-details"></h2> + <div class="d-flex justify-content-between align-items-center mt-3" *ngIf="displayLoginSuggestion"> + <div class="h6"><fa-icon [icon]="['fas', 'info-circle']" class="text-info" a11yRole="presentation"></fa-icon> {{ 'reservation-page.login.message' | translate }}</div> + <button type="button" (click)="login()" class="btn btn-default"><fa-icon [icon]="['fas', 'sign-in-alt']" a11yRole="presentation"></fa-icon> {{ 'user.menu.login' | translate }}</button> + </div> + </div> + + <div class="alert alert-danger" role="alert" *ngIf="globalErrors && globalErrors.length > 0"> + <div *ngFor="let err of globalErrors"><strong>{{err.code | translate: err.arguments}}</strong></div> + </div> + + <div class="row g-2 mb-3"> + <div class="col-12 col-sm-6"> + <div class="form-group"> + <label class="form-label" for="first-name">{{'common.first-name'|translate}}{{' '}}*</label> + <input id="first-name" class="form-control" formControlName="firstName" aria-required="true" type="text" #contactFirstName autocomplete="fname" [attr.maxlength]="255" appInvalidFeedback (change)="handleAutocomplete('firstName', contactFirstName.value)"> + </div> + </div> + <div class="col-12 col-sm-6"> + <div class="form-group"> + <label class="form-label" for="last-name">{{'common.last-name'|translate}}{{' '}}*</label> + <input id="last-name" class="form-control" formControlName="lastName" aria-required="true" type="text" #contactLastName autocomplete="lname" [attr.maxlength]="255" appInvalidFeedback (change)="handleAutocomplete('lastName', contactLastName.value)"> + </div> + </div> + </div> + <div class="row g-2 mb-3"> + <div class="col-12"> + <div class="form-group"> + <label class="form-label" for="email">{{'common.email'|translate}}{{' '}}*</label> + <input id="email" class="form-control" formControlName="email" aria-required="true" type="email" #contactLastEmail autocomplete="email" [attr.maxlength]="255" appInvalidFeedback (change)="handleAutocomplete('email', contactLastEmail.value)"> + </div> + </div> + </div> + </ng-container> + + <!-- invoicing section --> + <a id="invoiceSection" #invoiceAnchor></a> + <div *ngIf="!reservationInfo.orderSummary.free && purchaseContext.invoicingConfiguration.onlyInvoice" class="mb-4 mt-4"> + <app-invoice-form [form]="contactAndTicketsForm" [purchaseContext]="purchaseContext"></app-invoice-form> + </div> + <div *ngIf="!reservationInfo.orderSummary.free && !purchaseContext.invoicingConfiguration.onlyInvoice && purchaseContext.invoicingConfiguration.invoiceAllowed" class="mb-4 mt-4"> + <div class="form-check"> + <input class="form-check-input" type="checkbox" formControlName="invoiceRequested" (change)="handleInvoiceRequestedChange()" id="invoiceRequested"> + <label for="invoiceRequested" class="form-check-label"> + {{' '}}<span translate="reservation-page.i-need-an-invoice"></span> + </label> + </div> + + <app-invoice-form *ngIf="contactAndTicketsForm.value.invoiceRequested" [form]="contactAndTicketsForm" [purchaseContext]="purchaseContext"></app-invoice-form> + </div> + <!-- --> + + <ng-container *ngIf="purchaseContextType === 'event'"> + <h2 translate="reservation-page.attendees"></h2> + + <div class="form-check mb-3" *ngIf="!purchaseContext.assignmentConfiguration.forceAssignment && ticketCounts > 1 && !reservationInfo.containsCategoriesLinkedToGroups"> + <input class="form-check-input" type="checkbox" formControlName="postponeAssignment" id="postpone-assignment" value="true"> + <label class="form-check-label" for="postpone-assignment">{{' '}}{{'reservation-page.postpone-assignment'|translate}}</label> + </div> + + <ng-container *ngIf="!contactAndTicketsForm.value.postponeAssignment"> + <div *ngFor="let tc of reservationInfo.ticketsByCategory; let firstCategory = first; "> + <div *ngFor="let ticket of tc.tickets; let firstTicket = first;" formGroupName="tickets" class="card mt-4"> + <div class="attendees-data card-body"> + <div class="d-flex w-100 justify-content-between"> + <h3 class="card-title"> + <fa-icon [icon]="['fas', 'ticket-alt']" size="xs" [classes]="['rotate-45']" a11yRole="presentation"></fa-icon> + <span>{{' '}}{{'reservation-page-complete.ticket-nr' | translate}}<span class="ticket-counter"></span></span> + </h3> + <div class="h3" *ngIf="!enableAttendeeAutocomplete && firstCategory && firstTicket"> + <button class="btn btn-light btn-xs" + [ngbTooltip]="'reservation-page.copy-attendee'|translate" + type="button" + (click)="copyContactInfoTo(ticket)"> + <fa-icon [icon]="['far', 'clone']" a11yRole="presentation"></fa-icon> + <span class="sr-only" translate="reservation-page.copy-attendee"></span> + </button> + </div> + </div> + <div class="h3 mb-3"><small class="text-muted">{{tc.name}}</small></div> + <app-ticket-form [ticket]="ticket" [purchaseContext]="purchaseContext" [form]="getTicketForm(ticket)" [reservationMetadata]="reservationInfo.metadata"></app-ticket-form> + </div> + </div> + </div> + </ng-container> + </ng-container> + <ng-container *ngIf="purchaseContextType === 'subscription'"> + <div class="form-check mb-3"> + <input class="form-check-input" type="checkbox" formControlName="differentSubscriptionOwner" id="show-subscription-owner" value="true"> + <label class="form-check-label" for="show-subscription-owner">{{' '}}{{'reservation-page.subscription.different-owner' | translate}}</label> + </div> + <div *ngIf="contactAndTicketsForm.value.differentSubscriptionOwner" [formGroup]="getSubscriptionForm()"> + <div class="row g-2"> + <div class="col-12 col-md-6"> + <div class="form-group"> + <label class="form-label" for="subscription-first-name">{{'common.first-name' | translate}} *</label> + <input id="subscription-first-name" class="form-control" formControlName="firstName" type="text" appInvalidFeedback> + </div> + </div> + <div class="col-12 col-md-6"> + <div class="form-group"> + <label class="form-label" for="subscription-last-name">{{'common.last-name' | translate}} *</label> + <input class="form-control" id="subscription-last-name" formControlName="lastName" type="text" appInvalidFeedback> + </div> + </div> + <div class="col-12"> + <div class="form-group"> + <label class="form-label" class="form-label" for="subscription-email">{{'common.email' | translate}} *</label> + <input id="subscription-email" formControlName="email" type="email" [ngClass]="{'form-control': !emailEditForbidden, 'form-control-plaintext': emailEditForbidden}" appInvalidFeedback> + </div> + </div> + </div> + </div> + </ng-container> + + <div class="mt-3 mb-3" aria-hidden="true"><strong>*</strong>{{' '}}<span translate="common.required-fields"></span></div> + <hr class="mt-5"> + + <div class="row d-flex justify-content-between mobile-add-margin-bottom"> + <div class="col-md-5 order-md-1 col-12"> + <button type="submit" class="block-button btn btn-success" translate="reservation-page.continue" [disabled]="expired"></button> + </div> + <div class="col-md-5 order-md-0 col-12"> + <button type="button" class="block-button btn btn-light" (click)="cancelPendingReservation()" translate="reservation-page.cancel"></button> + </div> + </div> + </form> +</div> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/booking/booking.component.ts b/frontend/projects/public/src/app/reservation/booking/booking.component.ts new file mode 100644 index 0000000000..e5001c9a90 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/booking/booking.component.ts @@ -0,0 +1,327 @@ +import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {ReservationService} from '../../shared/reservation.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from '@angular/forms'; +import {TicketService} from '../../shared/ticket.service'; +import {BillingDetails, ItalianEInvoicing, ReservationInfo, ReservationSubscriptionInfo, TicketsByTicketCategory} from '../../model/reservation-info'; +import {Observable, of, Subject, zip} from 'rxjs'; +import {getErrorObject, handleServerSideValidationError} from '../../shared/validation-helper'; +import {I18nService} from '../../shared/i18n.service'; +import {Ticket} from '../../model/ticket'; +import {TranslateService} from '@ngx-translate/core'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {ErrorDescriptor} from '../../model/validated-response'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ReservationExpiredComponent} from '../expired-notification/reservation-expired.component'; +import {CancelReservationComponent} from '../cancel-reservation/cancel-reservation.component'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {PurchaseContext} from '../../model/purchase-context'; +import {WarningModalComponent} from '../../shared/warning-modal/warning-modal.component'; +import {SearchParams} from '../../model/search-params'; +import {UserService} from '../../shared/user.service'; +import {ANONYMOUS, User} from '../../model/user'; +import {first} from 'rxjs/operators'; +import {FeedbackService} from '../../shared/feedback/feedback.service'; +import {ReservationStatusChanged} from '../../model/embedding-configuration'; +import {embedded} from '../../shared/util'; + +@Component({ + selector: 'app-booking', + templateUrl: './booking.component.html' +}) +export class BookingComponent implements OnInit, AfterViewInit { + + reservationInfo: ReservationInfo; + purchaseContext: PurchaseContext; + contactAndTicketsForm: UntypedFormGroup; + private publicIdentifier: string; + reservationId: string; + expired: boolean; + globalErrors: ErrorDescriptor[]; + @ViewChild('invoiceAnchor') + private invoiceElement: ElementRef<HTMLAnchorElement>; + private doScroll = new Subject<boolean>(); + purchaseContextType: PurchaseContextType; + + ticketCounts: number; + + enableAttendeeAutocomplete: boolean; + displayLoginSuggestion: boolean; + + public static optionalGet<T>(billingDetails: BillingDetails, consumer: (b: ItalianEInvoicing) => T, userBillingDetails?: BillingDetails): T | null { + const italianEInvoicing = billingDetails.invoicingAdditionalInfo.italianEInvoicing; + if (italianEInvoicing != null) { + return consumer(italianEInvoicing); + } + const userItalianEInvoicing = userBillingDetails?.invoicingAdditionalInfo?.italianEInvoicing; + if (userItalianEInvoicing != null) { + return consumer(userItalianEInvoicing); + } + return null; + } + + private static isUUID(v: string): boolean { + const r = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; + return v.match(r) !== null; + } + + constructor( + private route: ActivatedRoute, + private router: Router, + private reservationService: ReservationService, + private ticketService: TicketService, + private purchaseContextService: PurchaseContextService, + private formBuilder: UntypedFormBuilder, + private i18nService: I18nService, + private translate: TranslateService, + private analytics: AnalyticsService, + private modalService: NgbModal, + private userService: UserService, + private feedbackService: FeedbackService) { } + + public ngOnInit(): void { + zip(this.route.data, this.route.params, this.userService.authenticationStatus.pipe(first())).subscribe(([data, params, authStatus]) => { + + const user: User | undefined = authStatus.user; + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + this.purchaseContextType = data.type; + this.displayLoginSuggestion = authStatus.enabled && authStatus.user === ANONYMOUS; + + zip( + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier), + this.reservationService.getReservationInfo(this.reservationId) + ).subscribe(([purchaseContext, reservationInfo]) => { + this.purchaseContext = purchaseContext; + this.reservationInfo = reservationInfo; + + this.i18nService.setPageTitle('reservation-page.header.title', purchaseContext); + + const invoiceRequested = purchaseContext.invoicingConfiguration.onlyInvoice ? true : reservationInfo.invoiceRequested; + + // + this.ticketCounts = 0; + this.reservationInfo.ticketsByCategory.forEach(t => { + this.ticketCounts += t.tickets.length; + }); + + + // auto complete (copy by default first/lastname + email to ticket) is enabled only if we have only + // one ticket + if (this.ticketCounts === 1 && this.purchaseContext.assignmentConfiguration.enableAttendeeAutocomplete) { + this.enableAttendeeAutocomplete = true; + } + // + + // + + const billingDetails = this.reservationInfo.billingDetails; + const userBillingDetails = user?.profile?.billingDetails; + + this.contactAndTicketsForm = this.formBuilder.group({ + firstName: this.formBuilder.control(this.reservationInfo.firstName || user?.firstName, [Validators.required, Validators.maxLength(255)]), + lastName: this.formBuilder.control(this.reservationInfo.lastName || user?.lastName, [Validators.required, Validators.maxLength(255)]), + email: this.formBuilder.control(this.reservationInfo.email || user?.emailAddress, [Validators.required, Validators.maxLength(255)]), + tickets: this.buildTicketsFormGroup(this.reservationInfo.ticketsByCategory, user), + invoiceRequested: invoiceRequested, + addCompanyBillingDetails: this.reservationInfo.addCompanyBillingDetails || userBillingDetails?.companyName != null, + billingAddressCompany: billingDetails.companyName || userBillingDetails?.companyName, + billingAddressLine1: billingDetails.addressLine1 || userBillingDetails?.addressLine1, + billingAddressLine2: billingDetails.addressLine2 || userBillingDetails?.addressLine2, + billingAddressZip: billingDetails.zip || userBillingDetails?.zip, + billingAddressCity: billingDetails.city || userBillingDetails?.city, + billingAddressState: billingDetails.state || userBillingDetails?.state, + vatCountryCode: billingDetails.country || userBillingDetails?.country, + customerReference: this.reservationInfo.customerReference, + vatNr: billingDetails.taxId || userBillingDetails?.taxId, + skipVatNr: this.reservationInfo.skipVatNr, + italyEInvoicingFiscalCode: BookingComponent.optionalGet(billingDetails, (i) => i.fiscalCode, userBillingDetails), + italyEInvoicingReferenceType: BookingComponent.optionalGet(billingDetails, (i) => i.referenceType, userBillingDetails), + italyEInvoicingReferenceAddresseeCode: BookingComponent.optionalGet(billingDetails, (i) => i.addresseeCode, userBillingDetails), + italyEInvoicingReferencePEC: BookingComponent.optionalGet(billingDetails, (i) => i.pec, userBillingDetails), + italyEInvoicingSplitPayment: BookingComponent.optionalGet(billingDetails, (i) => i.splitPayment, userBillingDetails), + postponeAssignment: false, + differentSubscriptionOwner: false, + subscriptionOwner: this.buildSubscriptionOwnerFormGroup(this.reservationInfo.subscriptionInfos, user) + }); + + setTimeout(() => this.doScroll.next(this.invoiceElement != null)); + + this.analytics.pageView(purchaseContext.analyticsConfiguration); + }); + }); + } + + ngAfterViewInit(): void { + zip(this.route.queryParams, this.doScroll.asObservable()) + .subscribe(results => { + const requestInvoice: boolean = !!results[0].requestInvoice; + if (requestInvoice && results[1]) { + this.contactAndTicketsForm.get('invoiceRequested').setValue(true); + this.invoiceElement.nativeElement.scrollIntoView(true); + } + }); + } + + private buildSubscriptionOwnerFormGroup(subscriptionInfos: Array<ReservationSubscriptionInfo> | undefined, user?: User): UntypedFormGroup { + if (subscriptionInfos != null) { + const subscriptionInfo = subscriptionInfos[0]; + const email = subscriptionInfo.owner?.email || (this.emailEditForbidden ? this.reservationInfo.email : null) || user?.emailAddress; + return this.formBuilder.group({ + firstName: subscriptionInfo.owner?.firstName || user?.firstName, + lastName: subscriptionInfo.owner?.lastName || user?.lastName, + email + }); + } else { + return null; + } + } + + private buildTicketsFormGroup(ticketsByCategory: TicketsByTicketCategory[], user: User): UntypedFormGroup { + const tickets = {}; + ticketsByCategory.forEach(t => { + t.tickets.forEach((ticket, idx) => { + tickets[ticket.uuid] = this.ticketService.buildFormGroupForTicket(ticket, idx === 0 ? user : undefined); + }); + }); + return this.formBuilder.group(tickets); + } + + private removeUnnecessaryFields(): void { + // check invoice data, remove company data if private invoice has been chosen + if (this.contactAndTicketsForm.get('invoiceRequested').value && !this.contactAndTicketsForm.get('addCompanyBillingDetails').value) { + ['billingAddressCompany', 'vatNr', 'skipVatNr'].forEach(n => this.contactAndTicketsForm.get(n).setValue(null)); + } + } + + submitForm(): void { + this.removeUnnecessaryFields(); + this.validateToOverview(false); + } + + private validateToOverview(ignoreWarnings: boolean): void { + this.reservationService.validateToOverview(this.reservationId, this.contactAndTicketsForm.value, this.translate.currentLang, ignoreWarnings).subscribe(res => { + if (res.success && (!res.warnings || res.warnings.length === 0 || ignoreWarnings)) { + let o: Observable<unknown> = of(true); + if (this.route.snapshot.queryParamMap.has('subscription') && BookingComponent.isUUID(this.route.snapshot.queryParamMap.get('subscription'))) { + // try to apply the subscription + const subscriptionCode = this.route.snapshot.queryParamMap.get('subscription'); + o = this.reservationService.applySubscriptionCode(this.reservationId, subscriptionCode, this.reservationInfo.email); + } + o.subscribe( + _ => this.proceedToOverview(), + // if there is an error, we proceed anyway + () => this.proceedToOverview() + ); + } else if (res.success) { + // display warnings + const modalRef = this.modalService.open(WarningModalComponent, {centered: true, backdrop: 'static'}); + const firstWarning = res.warnings[0]; + modalRef.componentInstance.message = firstWarning.code; + const params: {[key: string]: string} = {}; + firstWarning.params.forEach((v, i) => params['' + i] = v); + modalRef.componentInstance.parameters = params; + modalRef.result.then(() => this.validateToOverview(true)); + } + }, (err) => { + this.globalErrors = handleServerSideValidationError(err, this.contactAndTicketsForm); + }); + } + + private proceedToOverview(): Promise<boolean> { + return this.router.navigate([this.purchaseContextType, this.publicIdentifier, 'reservation', this.reservationId, 'overview'], { + queryParams: SearchParams.transformParams(this.route.snapshot.queryParams, this.route.snapshot.params) + }); + } + + cancelPendingReservation() { + this.modalService.open(CancelReservationComponent, {centered: true}).result.then(res => { + if (res === 'yes') { + this.reservationService.cancelPendingReservation(this.reservationId).subscribe(() => { + this.handleCancelOrExpired(); + }); + } + }, () => {}); + } + + handleExpired(expired: boolean): void { + setTimeout(() => { + if (!this.expired) { + this.expired = expired; + this.modalService.open(ReservationExpiredComponent, {centered: true, backdrop: 'static'}) + .result.then(() => this.handleCancelOrExpired()); + } + }); + } + + private handleCancelOrExpired(): void { + if (embedded && this.purchaseContext.embeddingConfiguration.enabled) { + window.parent.postMessage( + new ReservationStatusChanged('CANCELLED', this.reservationId), + this.purchaseContext.embeddingConfiguration.notificationOrigin + ); + } else { + this.router.navigate([this.purchaseContextType, this.publicIdentifier], {replaceUrl: true}); + } + } + + handleInvoiceRequestedChange() { + // set addCompanyBillingDetails to false if it's null + if (this.contactAndTicketsForm.value.addCompanyBillingDetails === null) { + this.contactAndTicketsForm.get('addCompanyBillingDetails').setValue(false); + } + } + + handleAutocomplete(fieldName: string, value: string) { + if (this.enableAttendeeAutocomplete) { + const ticketUUID = Object.keys(this.contactAndTicketsForm.get('tickets').value)[0]; + const targetControl = this.contactAndTicketsForm.get(`tickets.${ticketUUID}.${fieldName}`); + if (targetControl.pristine && (targetControl.value == null || targetControl.value === '')) { + targetControl.setValue(value); + } + } + } + + getTicketForm(ticket: Ticket): UntypedFormGroup { + return this.contactAndTicketsForm.get('tickets.' + ticket.uuid) as UntypedFormGroup; + } + + getSubscriptionForm(): UntypedFormGroup { + return this.contactAndTicketsForm.get('subscriptionOwner') as UntypedFormGroup; + } + + copyContactInfoTo(ticket: Ticket) { + ['firstName', 'lastName', 'email'].forEach(field => { + const val = this.contactAndTicketsForm.get(field).value; + this.contactAndTicketsForm.get(`tickets.${ticket.uuid}.${field}`).setValue(val); + }); + } + + login(): void { + // save reservation status + const redirectToLogin = () => { + window.location.href = `/openid/authentication?reservation=${this.reservationId}&contextType=${this.purchaseContextType}&id=${this.publicIdentifier}`; + }; + this.reservationService.validateToOverview(this.reservationId, this.contactAndTicketsForm.value, this.translate.currentLang, false) + .subscribe(() => { + // reservation is now saved. We can proceed to login + redirectToLogin(); + }, error => { + const errorObj = getErrorObject(error); + if (errorObj != null) { + // reservation is not in a valid state. Proceed anyway + redirectToLogin(); + } else { + this.feedbackService.showError(this.translate.instant('reservation-page.cannot-login.error')); + } + }); + } + + get showContactData(): boolean { + return !embedded || !this.reservationInfo.metadata.hideContactData; + } + + get emailEditForbidden(): boolean { + return this.reservationInfo.metadata.lockEmailEdit; + } +} diff --git a/frontend/projects/public/src/app/reservation/cancel-reservation/cancel-reservation.component.ts b/frontend/projects/public/src/app/reservation/cancel-reservation/cancel-reservation.component.ts new file mode 100644 index 0000000000..6c404fd3dd --- /dev/null +++ b/frontend/projects/public/src/app/reservation/cancel-reservation/cancel-reservation.component.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-cancel-reservation', + templateUrl: './cancel-reservation.html' +}) +export class CancelReservationComponent { + constructor(public activeModal: NgbActiveModal) { + } +} diff --git a/frontend/projects/public/src/app/reservation/cancel-reservation/cancel-reservation.html b/frontend/projects/public/src/app/reservation/cancel-reservation/cancel-reservation.html new file mode 100644 index 0000000000..76b2ead888 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/cancel-reservation/cancel-reservation.html @@ -0,0 +1,12 @@ +<div class="modal-body text-center"> + <h4 class="modal-title text-danger text-center"><fa-icon [icon]="['fas', 'exclamation-triangle']" a11yRole="presentation"></fa-icon>{{' '}}{{'reservation-page.cancel-reservation.confirmation.text' | translate}}</h4> + <hr/> + <div class="row"> + <div class="col-6"> + <button type="button" class="btn btn-light block-button" (click)="activeModal.close('no')" translate="reservation-page-complete.confirm-cancellation.button.no"></button> + </div> + <div class="col-6"> + <button type="button" class="btn btn-danger block-button" (click)="activeModal.close('yes')" translate="reservation-page-complete.confirm-cancellation.button.yes"></button> + </div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/deferred-offline-payment/deferred-offline-payment.component.html b/frontend/projects/public/src/app/reservation/deferred-offline-payment/deferred-offline-payment.component.html new file mode 100644 index 0000000000..49a1368bbd --- /dev/null +++ b/frontend/projects/public/src/app/reservation/deferred-offline-payment/deferred-offline-payment.component.html @@ -0,0 +1,22 @@ +<app-reservation> + <div *ngIf="purchaseContext && reservationInfo"> + <div class="alert alert-success mt-5"> + <h2 class="text-center" [innerHTML]="'reservation-page.offline.deferred.success.title' | translate"></h2> + <h4 class="text-center" translate="reservation-page.offline.deferred.success.message"></h4> + </div> + <div class="page-header"> + <h2 translate="reservation-page.title"></h2> + </div> + <app-summary-table [purchaseContext]="purchaseContext" [reservationInfo]="reservationInfo"></app-summary-table> + + <div class="text-center text-muted mt-5">{{'reservation-page-complete.order-information' | translate: {'0': reservationId, '1': reservationInfo.firstName + ' ' + reservationInfo.lastName} }}</div> + <hr> + <div class="row d-flex justify-content-between mt-2 mobile-add-margin-bottom mt-4"> + <div class="col-md-5 col-12 order-md-1" *ngIf="purchaseContext.invoicingConfiguration.userCanDownloadReceiptOrInvoice && reservationInfo.invoiceNumber !== null"> + <a [href]="'/api/v2/public/' + purchaseContextType + '/' + publicIdentifier + '/reservation/' + reservationInfo.id + '/invoice'" class="btn btn-success block-button" target="_blank" translate="reservation-page-complete.download-your-invoice"></a> + </div> + <div class="col-md-5 col-12 order-md-1 text-center" *ngIf="!purchaseContext.invoicingConfiguration.userCanDownloadReceiptOrInvoice"><p translate="reservation-page-waiting.invoice-will-be-sent"></p></div> + <div class="col-md-5 col-12 order-md-0"><a [href]="purchaseContext.websiteUrl" class="btn btn-light block-button" translate="to-event-site"></a></div> + </div> + </div> +</app-reservation> \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/deferred-offline-payment/deferred-offline-payment.component.ts b/frontend/projects/public/src/app/reservation/deferred-offline-payment/deferred-offline-payment.component.ts new file mode 100644 index 0000000000..22d0066944 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/deferred-offline-payment/deferred-offline-payment.component.ts @@ -0,0 +1,48 @@ +import {Component, OnInit} from '@angular/core'; +import {ReservationInfo} from '../../model/reservation-info'; +import {ActivatedRoute} from '@angular/router'; +import {ReservationService} from '../../shared/reservation.service'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../../shared/i18n.service'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {zip} from 'rxjs'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {PurchaseContext} from '../../model/purchase-context'; + +@Component({ + selector: 'app-deferred-offline-payment', + templateUrl: './deferred-offline-payment.component.html' +}) +export class DeferredOfflinePaymentComponent implements OnInit { + + reservationInfo: ReservationInfo; + purchaseContextType: PurchaseContextType; + publicIdentifier: string; + reservationId: string; + purchaseContext: PurchaseContext; + + constructor( + private route: ActivatedRoute, + private purchaseContextService: PurchaseContextService, + private reservationService: ReservationService, + public translate: TranslateService, + private i18nService: I18nService, + private analytics: AnalyticsService) { } + + ngOnInit(): void { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.purchaseContextType = data.type; + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + zip( + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier), + this.reservationService.getReservationInfo(this.reservationId) + ).subscribe(([ev, reservationInfo]) => { + this.purchaseContext = ev; + this.reservationInfo = reservationInfo; + this.i18nService.setPageTitle('reservation-page-waiting.header.title', ev); + this.analytics.pageView(ev.analyticsConfiguration); + }); + }); + } +} diff --git a/frontend/projects/public/src/app/reservation/download-ticket/download-ticket.component.html b/frontend/projects/public/src/app/reservation/download-ticket/download-ticket.component.html new file mode 100644 index 0000000000..58740ac589 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/download-ticket/download-ticket.component.html @@ -0,0 +1,29 @@ +<div class="modal-header"> + <h2 class="modal-title">{{ticket.fullName}}</h2> +</div> +<div class="modal-body"> + <div class="row mt-5 mb-5"> + <div class="col-8 offset-2 col-md-6 offset-md-3 align-self-center mb-5" *ngIf="gWalletEnabled"> + <a target="_blank" rel="noopener" (click)="close()" [attr.href]="'/api/wallet/event/' + eventName + '/v1/version/passes/'+ticket.uuid"> + <img src="/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.svg" class="img-responsive mx-auto" alt="Add to Google Wallet"> + </a> + </div> + + <div class="col-8 offset-2 col-md-6 offset-md-3 align-self-center mb-5" *ngIf="passEnabled"> + <a target="_blank" rel="noopener" (click)="close()" [attr.href]="'/api/pass/event/' + eventName + '/v1/version/passes/'+ticket.uuid"> + <img src="/resources/images/email/add-to-apple-wallet-button.png" class="img-responsive mx-auto" alt="Add to Apple Wallet"> + </a> + </div> + + <div class="col-8 offset-2 col-md-6 offset-md-3 align-self-center"> + <a target="_blank" (click)="close()" rel="noopener" [attr.href]="'/api/v2/public/event/' + eventName + '/ticket/' + ticket.uuid + '/download-ticket'" class="btn btn-link btn-block d-flex justify-content-center align-items-center"> + <div class="mr-3"><fa-icon size="2x" [icon]="['fas', 'file-pdf']" a11yRole="presentation"></fa-icon></div> + <div>{{'reservation-page-complete.download-ticket'|translate}} PDF</div> + </a> + </div> + + </div> +</div> +<div class="modal-footer"> + <button type="button" (click)="close()" class="btn btn-light btn-block" translate="common.close"></button> +</div> diff --git a/frontend/projects/public/src/app/reservation/download-ticket/download-ticket.component.ts b/frontend/projects/public/src/app/reservation/download-ticket/download-ticket.component.ts new file mode 100644 index 0000000000..4d602dc4d7 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/download-ticket/download-ticket.component.ts @@ -0,0 +1,34 @@ +import {Component, Input} from '@angular/core'; +import {Ticket} from '../../model/ticket'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; +import {WalletConfiguration} from '../../model/info'; + +@Component({ + selector: 'app-download-ticket', + templateUrl: './download-ticket.component.html' +}) +export class DownloadTicketComponent { + @Input() + ticket: Ticket; + + @Input() + eventName: string; + + @Input() + walletConfiguration: WalletConfiguration; + + constructor(private activeModal: NgbActiveModal) { + } + + close(): void { + this.activeModal.dismiss(); + } + + get gWalletEnabled(): boolean { + return this.walletConfiguration != null && this.walletConfiguration.gWalletEnabled; + } + + get passEnabled(): boolean { + return this.walletConfiguration != null && this.walletConfiguration.passEnabled; + } +} diff --git a/frontend/projects/public/src/app/reservation/error/error.component.html b/frontend/projects/public/src/app/reservation/error/error.component.html new file mode 100644 index 0000000000..d3aab16b76 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/error/error.component.html @@ -0,0 +1,5 @@ +<app-reservation> + <h4 class="mt-5 text-center" translate="reservation-page-error-status.title"></h4> + <p class="mt-5 text-center" *ngIf="purchaseContext" [innerHTML]="'reservation-page-error-status.contact-text' | translate: {'0': purchaseContext.organizationEmail, '1': reservationId, '2': purchaseContext.organizationEmail}"></p> + <p class="mt-5 text-center">{{'reservation-page-error-status.your-reservation-identifier' | translate: {'0': reservationId} }}</p> +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/error/error.component.ts b/frontend/projects/public/src/app/reservation/error/error.component.ts new file mode 100644 index 0000000000..2a3fd836f6 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/error/error.component.ts @@ -0,0 +1,35 @@ +import {Component, OnInit} from '@angular/core'; +import {I18nService} from '../../shared/i18n.service'; +import {ActivatedRoute} from '@angular/router'; +import {PurchaseContext} from '../../model/purchase-context'; +import {zip} from 'rxjs'; +import {PurchaseContextService} from '../../shared/purchase-context.service'; + +@Component({ + selector: 'app-error', + templateUrl: './error.component.html' +}) +export class ErrorComponent implements OnInit { + + + reservationId: string; + purchaseContext: PurchaseContext; + + constructor( + private route: ActivatedRoute, + private purchaseContextService: PurchaseContextService, + private i18nService: I18nService) { } + + ngOnInit() { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + const publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + const purchaseContextType = data.type; + this.purchaseContextService.getContext(purchaseContextType, publicIdentifier).subscribe(ev => { + this.purchaseContext = ev; + this.i18nService.setPageTitle('reservation-page-not-found.header.title', ev); + }); + }); + } + +} diff --git a/frontend/projects/public/src/app/reservation/expired-notification/reservation-expired.component.html b/frontend/projects/public/src/app/reservation/expired-notification/reservation-expired.component.html new file mode 100644 index 0000000000..9a1c9019f7 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/expired-notification/reservation-expired.component.html @@ -0,0 +1,9 @@ +<div class="modal-body text-center"> + <h4 class="modal-title text-danger text-center"><fa-icon [icon]="['far', 'clock']" a11yRole="presentation"></fa-icon>{{' '}}{{'session-expired.header.title' | translate}}</h4> + <hr/> + <div class="row"> + <div class="col-6 offset-3"> + <button type="button" class="btn btn-light block-button" (click)="activeModal.close('to-event-site')" translate="to-home"></button> + </div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/expired-notification/reservation-expired.component.ts b/frontend/projects/public/src/app/reservation/expired-notification/reservation-expired.component.ts new file mode 100644 index 0000000000..e36aa73dc3 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/expired-notification/reservation-expired.component.ts @@ -0,0 +1,13 @@ +import {Component, Input} from '@angular/core'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-reservation-expired', + templateUrl: './reservation-expired.component.html' +}) +export class ReservationExpiredComponent { + @Input() + name: string; + + constructor(public activeModal: NgbActiveModal) {} +} diff --git a/frontend/projects/public/src/app/reservation/invoice-form/invoice-form.component.html b/frontend/projects/public/src/app/reservation/invoice-form/invoice-form.component.html new file mode 100644 index 0000000000..66b430ae77 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/invoice-form/invoice-form.component.html @@ -0,0 +1,144 @@ +<div class="page-header"> + <h2 translate="reservation-page.invoice-details"></h2> +</div> + +<div [formGroup]="form"> + <div class="row g-2"> + <div class="col-12"> + <div class="form-group"> + <label translate="invoice.type" id="invoiceTypeLabel"></label> + <div> + <div class="form-check form-check-inline"> + <input class="form-check-input" type="radio" role="radiogroup" aria-labelledby="invoiceTypeLabel" formControlName="addCompanyBillingDetails" [value]="false" id="invoiceTypePrivate" > + <label class="form-check-label radio-inline" for="invoiceTypePrivate">{{' '}}{{'invoice.type.private' | translate}}</label> + </div> + <div class="form-check form-check-inline"> + <input class="form-check-input" type="radio" role="radiogroup" aria-labelledby="invoiceTypeLabel" formControlName="addCompanyBillingDetails" [value]="true" id="invoiceTypeBusiness"> + <label class="form-check-label radio-inline" for="invoiceTypeBusiness">{{' '}}{{'invoice.type.business' | translate}}</label> + </div> + </div> + </div> + </div> + <div class="col-12" *ngIf="invoiceBusiness"> + <div class="form-group"> + <label for="billingAddressCompany" class="form-label" translate="reservation-page-complete.company"></label> + <input id="billingAddressCompany" formControlName="billingAddressCompany" class="form-control" type="text" [attr.maxlength]="255" appInvalidFeedback> + </div> + </div> + <div class="col-12"> + <div class="form-group"> + <label for="billingAddressLine1" class="form-label">{{'reservation-page.address-line-1' | translate}}{{' *'}}</label> + <input id="billingAddressLine1" aria-required="true" formControlName="billingAddressLine1" class="form-control" type="text" required [attr.maxlength]="255" appInvalidFeedback> + </div> + </div> + <div class="col-12"> + <div class="form-group"> + <label for="billingAddressLine2" class="form-label">{{'reservation-page.address-line-2' | translate}}</label> + <input id="billingAddressLine2" formControlName="billingAddressLine2" class="form-control" type="text" [attr.maxlength]="255" appInvalidFeedback> + </div> + </div> + <div class="col-12 col-md-3"> + <div class="form-group"> + <label for="billingAddressZip" class="form-label">{{'reservation-page.zip-postal-code' | translate}}{{' *'}}</label> + <input id="billingAddressZip" aria-required="true" formControlName="billingAddressZip" class="form-control" type="text" required [attr.maxlength]="50" appInvalidFeedback> + </div> + </div> + <div class="col-12 col-md-6"> + <div class="form-group"> + <label for="billingAddressCity" class="form-label">{{'reservation-page.city' | translate}}{{' *'}}</label> + <input id="billingAddressCity" aria-required="true" formControlName="billingAddressCity" class="form-control" type="text" required [attr.maxlength]="255" appInvalidFeedback> + </div> + </div> + <div class="col-12 col-md-3"> + <div class="form-group"> + <label class="form-label" for="billingAddressState">{{'reservation-page.state'|translate}}<span *ngIf="enabledItalyEInvoicing"> *</span></label> + <input id="billingAddressState" [attr.required]="enabledItalyEInvoicing" [attr.aria-required]="enabledItalyEInvoicing" type="text" formControlName="billingAddressState" class="form-control" appInvalidFeedback> + </div> + </div> + <div class="col-12" [ngClass]="{'col-md-6': customerReferenceEnabled && invoiceBusiness}"> + <div class="form-group"> + <label for="vatCountry" id="vatCountryLabel" class="form-label">{{'reservation-page-complete.country' | translate}}{{' *'}}</label> + <ng-container> + <ng-select *ngIf="!isMobile"aria-required="true" placeholder="{{'reservation-page.country.select'|translate}}" + [items]="countries" bindLabel="name" bindValue="isoCode" [selectOnTab]="true" + formControlName="vatCountryCode" class="form-control field-required" + [searchFn]="searchCountry" required appInvalidFeedback + labelForId="vatCountry" aria-labelledby="vatCountryLabel" [inputAttrs]="{autocomplete: 'nope'}"> + <ng-template ng-option-tmp let-item="item">{{item.name}} ({{item.isoCode}})</ng-template> + <ng-template ng-label-tmp let-item="item">{{item.name}} ({{item.isoCode}})</ng-template> + <ng-template ng-notfound-tmp let-searchTerm="searchTerm"></ng-template> + </ng-select><select *ngIf="isMobile" id="vatCountry" [attr.aria-required]="true" class="custom-select" formControlName="vatCountryCode" appInvalidFeedback> + <option value=""></option> + <option *ngFor="let c of countries" [ngValue]="c.isoCode">{{c.name}} ({{c.isoCode}})</option> + </select> + </ng-container> + </div> + </div> + + <div *ngIf="customerReferenceEnabled && invoiceBusiness" class="col-12 col-md-6"> + <div class="form-group"> + <label for="customerReference" class="form-label" translate="common.customer-reference"></label> + <input type="text" formControlName="customerReference" id="customerReference" class="form-control" [attr.maxlength]="50" appInvalidFeedback> + </div> + </div> + + <ng-container *ngIf="invoiceBusiness"> + <div [ngClass]="{'col-md-12': vatNumberStrictlyRequired, 'col-md-6': !vatNumberStrictlyRequired}"> + <div class="form-group"> + <label class="form-label" for="vatNr">{{'invoice.vat' |translate: {'0': ('common.vat' | translate)} }}<span *ngIf="taxIdIsRequired"> *</span></label> + <div class="input-group"> + <div class="input-group-prepend" *ngIf="form.value.vatCountryCode"><span class="input-group-text">{{form.value.vatCountryCode}}</span></div> + <input id="vatNr" type="text" formControlName="vatNr" class="form-control" appInvalidFeedback [attr.aria-required]="taxIdIsRequired"> + </div> + </div> + </div> + <div class="col-md-6" *ngIf="!vatNumberStrictlyRequired"> + <div class="form-check checkbox-in-form-group"> + <input class="form-check-input" type="checkbox" formControlName="skipVatNr" [value]="true" id="skip-vat-nr" appInvalidFeedback> + <label class="form-check-label" for="skip-vat-nr">{{' '}}{{'reservation-page.skipVatNr' | translate: {'0': ('common.vat' | translate)} }}</label> + </div> + </div> + </ng-container> + <ng-container *ngIf="enabledItalyEInvoicing && countrySelected"> + <div class="col-12" id="italyEInvoicingTaxId"> + <div class="form-group"> + <label class="form-label" for="italyEInvoicingFiscalCode">{{ (italyEInvoicingFormDisplayed ? 'invoice-fields.fiscalCode' : 'invoice-fields.tax-id') | translate}}{{' *'}}</label> + <input id="italyEInvoicingFiscalCode" required aria-required="true" type="text" formControlName="italyEInvoicingFiscalCode" class="form-control" appInvalidFeedback> + </div> + </div> + </ng-container> + + <ng-container *ngIf="italyEInvoicingFormDisplayed"> + <div class="col-12 col-md-6"> + <div class="form-group"> + <label class="form-label" for="italyEInvoicingReferenceType">{{'invoice-fields.addresseeItalyEInvoice' | translate}}{{' *'}}</label> + <select class="form-select" id="italyEInvoicingReferenceType" required aria-required="true" formControlName="italyEInvoicingReferenceType"> + <option value="ADDRESSEE_CODE" translate="invoice-fields.addressee-code"></option> + <option value="PEC" translate="invoice-fields.pec"></option> + <option value="NONE" translate="invoice.fields.neither"></option> + </select> + </div> + </div> + <div class="col-12 col-md-6" *ngIf="addresseeCodeSelected || pecSelected"> + <div class="form-group" *ngIf="addresseeCodeSelected"> + <label class="form-label" for="italyEInvoicingReferenceAddresseeCode">{{'invoice-fields.addressee-code'|translate}}{{' *'}}</label> + <input id="italyEInvoicingReferenceAddresseeCode" required aria-required="true" type="text" [attr.maxlength]="7" class="form-control" formControlName="italyEInvoicingReferenceAddresseeCode" appInvalidFeedback> + </div> + <div class="form-group" *ngIf="pecSelected"> + <label class="form-label" for="italyEInvoicingReferencePEC">{{'invoice-fields.pec' | translate}}{{' *'}}</label> + <input id="italyEInvoicingReferencePEC" type="email" required aria-required="true" class="form-control" formControlName="italyEInvoicingReferencePEC" appInvalidFeedback> + </div> + </div> + + <ng-container *ngIf="invoiceBusiness"> + <div class="col-12"> + <div class="form-check"> + <input class="form-check-input" id="italyEInvoicingSplitPayment" formControlName="italyEInvoicingSplitPayment" type="checkbox" aria-labelledby="split-payment-label"> + <label class="form-check-label" for="italyEInvoicingSplitPayment" id="split-payment-label">{{'invoice-fields.split-payment' | translate}}</label> + </div> + </div> + </ng-container> + </ng-container> + + </div> +</div> diff --git a/frontend/projects/public/src/app/reservation/invoice-form/invoice-form.component.ts b/frontend/projects/public/src/app/reservation/invoice-form/invoice-form.component.ts new file mode 100644 index 0000000000..5f521ef334 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/invoice-form/invoice-form.component.ts @@ -0,0 +1,126 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {UntypedFormGroup} from '@angular/forms'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../../shared/i18n.service'; +import {Subscription} from 'rxjs'; +import {LocalizedCountry} from '../../model/localized-country'; +import {PurchaseContext} from '../../model/purchase-context'; +import {InvoicingConfiguration} from '../../model/event'; +import {mobile} from '../../shared/util'; + +@Component({ + selector: 'app-invoice-form', + templateUrl: './invoice-form.component.html' +}) +export class InvoiceFormComponent implements OnInit, OnDestroy { + + @Input() + form: UntypedFormGroup; + + @Input() + purchaseContext?: PurchaseContext; + + @Input() + invoicingConfiguration?: InvoicingConfiguration; + + private langChangeSub: Subscription; + + countries: LocalizedCountry[]; + + taxIdIsRequired = true; + + isMobile = mobile; + + constructor(private translate: TranslateService, private i18nService: I18nService) { } + + ngOnInit(): void { + this.getCountries(this.translate.currentLang); + this.langChangeSub = this.translate.onLangChange.subscribe(change => { + this.getCountries(this.translate.currentLang); + }); + + this.updateItalyEInvoicingFields(); + + this.form.get('italyEInvoicingReferenceType').valueChanges.subscribe(change => { + this.updateItalyEInvoicingFields(); + }); + this.form.get('skipVatNr')?.valueChanges.subscribe(change => { + this.taxIdIsRequired = !change; + }); + } + + public ngOnDestroy(): void { + if (this.langChangeSub) { + this.langChangeSub.unsubscribe(); + } + } + + + updateItalyEInvoicingFields(): void { + const refType = this.form.get('italyEInvoicingReferenceType').value; + if (refType === 'ADDRESSEE_CODE') { + this.form.get('italyEInvoicingReferencePEC').setValue(null); + } else if (refType === 'PEC') { + this.form.get('italyEInvoicingReferenceAddresseeCode').setValue(null); + } else if (refType === 'NONE') { + this.form.get('italyEInvoicingReferencePEC').setValue(null); + this.form.get('italyEInvoicingReferenceAddresseeCode').setValue(null); + } + } + + get addresseeCodeSelected(): boolean { + return this.form.get('italyEInvoicingReferenceType').value === 'ADDRESSEE_CODE'; + } + + get pecSelected(): boolean { + return this.form.get('italyEInvoicingReferenceType').value === 'PEC'; + } + + getCountries(currentLang: string): void { + this.i18nService.getVatCountries(currentLang).subscribe(countries => { + this.countries = countries; + }); + } + + get euVatCheckingEnabled(): boolean { + return this.invoicingConf.euVatCheckingEnabled; + } + + get customerReferenceEnabled(): boolean { + return this.invoicingConf.customerReferenceEnabled; + } + + get invoiceBusiness(): boolean { + return this.form.value.addCompanyBillingDetails; + } + + get vatNumberStrictlyRequired(): boolean { + return this.invoicingConf.vatNumberStrictlyRequired; + } + + get enabledItalyEInvoicing(): boolean { + return this.invoicingConf.enabledItalyEInvoicing; + } + + get italyEInvoicingFormDisplayed(): boolean { + return this.enabledItalyEInvoicing && this.form.value.vatCountryCode === 'IT'; + } + + get countrySelected(): boolean { + return this.form.value.vatCountryCode != null; + } + + private get invoicingConf(): InvoicingConfiguration { + return this.purchaseContext?.invoicingConfiguration || this.invoicingConfiguration; + } + + searchCountry(term: string, country: LocalizedCountry): boolean { + if (term) { + term = term.toLowerCase(); + return country.isoCode.toLowerCase().indexOf(term) > -1 || country.name.toLowerCase().indexOf(term) > -1; + } + return true; + } + + protected readonly mobile = mobile; +} diff --git a/frontend/projects/public/src/app/reservation/modal-remove-subscription/modal-remove-subscription.component.html b/frontend/projects/public/src/app/reservation/modal-remove-subscription/modal-remove-subscription.component.html new file mode 100644 index 0000000000..484ba132a8 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/modal-remove-subscription/modal-remove-subscription.component.html @@ -0,0 +1,12 @@ +<div class="modal-body text-center"> + <h4 class="modal-title text-danger text-center"><fa-icon [icon]="['fas', 'exclamation-triangle']" a11yRole="presentation"></fa-icon>{{' '}}{{'reservation-page.overview.modal.remove-subscription' | translate}}</h4> + <hr/> + <div class="row"> + <div class="col-6"> + <button type="button" class="btn btn-light block-button" (click)="activeModal.close(false)" translate="reservation-page-complete.confirm-cancellation.button.no"></button> + </div> + <div class="col-6"> + <button type="button" class="btn btn-danger block-button" (click)="activeModal.close(true)" translate="reservation-page-complete.confirm-cancellation.button.yes"></button> + </div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/modal-remove-subscription/modal-remove-subscription.component.ts b/frontend/projects/public/src/app/reservation/modal-remove-subscription/modal-remove-subscription.component.ts new file mode 100644 index 0000000000..a60e89499a --- /dev/null +++ b/frontend/projects/public/src/app/reservation/modal-remove-subscription/modal-remove-subscription.component.ts @@ -0,0 +1,12 @@ +import {Component} from '@angular/core'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-modal-remove-subscription', + templateUrl: './modal-remove-subscription.component.html' +}) +export class ModalRemoveSubscriptionComponent { + + constructor(public activeModal: NgbActiveModal) { } + +} diff --git a/frontend/projects/public/src/app/reservation/not-found/not-found.component.html b/frontend/projects/public/src/app/reservation/not-found/not-found.component.html new file mode 100644 index 0000000000..8d31ce4876 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/not-found/not-found.component.html @@ -0,0 +1,6 @@ +<app-reservation> + +<h4 class="mt-5 text-center">{{'reservation-page-not-found.reservation-with-id-does-not-exist'|translate: {'0': reservationId|uppercase|slice:0:8} }}</h4> +<h5 class="mt-5 text-center" [innerHTML]="'reservation-page-not-found.go-back-to-event' | translate: {'0': purchaseContextUrl}"></h5> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/not-found/not-found.component.ts b/frontend/projects/public/src/app/reservation/not-found/not-found.component.ts new file mode 100644 index 0000000000..03e02849a6 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/not-found/not-found.component.ts @@ -0,0 +1,36 @@ +import {Component, OnInit} from '@angular/core'; +import {I18nService} from '../../shared/i18n.service'; +import {ActivatedRoute, Router, UrlSerializer} from '@angular/router'; +import {zip} from 'rxjs'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; + +@Component({ + selector: 'app-not-found', + templateUrl: './not-found.component.html' +}) +export class NotFoundComponent implements OnInit { + + private purchaseContextType: PurchaseContextType; + private publicIdentifier: string; + reservationId: string; + purchaseContextUrl: string; + + constructor( + private route: ActivatedRoute, + private router: Router, + private serializer: UrlSerializer, + private purchaseContextService: PurchaseContextService, + private i18nService: I18nService) { } + + ngOnInit() { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.purchaseContextType = data.type; + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + this.purchaseContextUrl = this.serializer.serialize(this.router.createUrlTree([this.purchaseContextType, this.publicIdentifier])); + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier).subscribe(ev => { + this.i18nService.setPageTitle('reservation-page-not-found.header.title', ev); + }); + }); + } +} diff --git a/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.html b/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.html new file mode 100644 index 0000000000..3dfc1d06fe --- /dev/null +++ b/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.html @@ -0,0 +1,59 @@ +<app-reservation> + +<div class="mb-2 mt-2 center-block" *ngIf="purchaseContext && reservationInfo"> + + <div class="alert alert-warning mt-2 mb-2 text-center"> + <h2 *ngIf="reservationFinalized"><fa-icon [icon]="['fas', 'exclamation-triangle']" a11yRole="presentation"></fa-icon>{{' '}}<span [innerHTML]="'reservation-page-waiting.title' | translate: {'0': reservationInfo.formattedExpirationDate[translate.currentLang]}"></span></h2> + + </div> + + <div class="page-header"> + <h2 translate="reservation-page.title"></h2> + </div> + <app-summary-table [purchaseContext]="purchaseContext" [reservationInfo]="reservationInfo"></app-summary-table> + + <ng-container *ngIf="reservationFinalized"> + + <h4 [innerHTML]="'reservation-page-waiting.required-steps'|translate" class="mt-5"></h4> + + <ng-template #basicInstruction> + <li *ngIf="purchaseContext.bankAccountOwner.length > 0"> + <p class="no-margin-bottom" [innerHTML]="'reservation-page-waiting.required-steps.1.with-bank-account-owner' | translate: {'0': purchaseContext.currency+' '+reservationInfo.orderSummary.totalPrice, '1': purchaseContext.bankAccount}"></p> + <p class="bank-account-owner-info" *ngFor="let ownerLine of purchaseContext.bankAccountOwner">{{ownerLine}}</p> + <p class="no-margin-bottom" [innerHTML]="'reservation-page-waiting.required-steps.1.with-bank-account-owner.2' | translate: {'0': paymentReason}"></p> + </li> + <li *ngIf="purchaseContext.bankAccountOwner.length === 0" + [innerHTML]="'reservation-page-waiting.required-steps.1' | translate: {'0': purchaseContext.currency+' '+reservationInfo.orderSummary.totalPrice, '1': purchaseContext.bankAccount, '2': paymentReason}"> + </li> + </ng-template> + <ng-container *ngIf="purchaseContext.offlinePaymentConfiguration?.showOnlyBasicInstructions"> + <ul class="mt-3 list-unstyled"> + <ng-container *ngTemplateOutlet="basicInstruction"></ng-container> + </ul> + </ng-container> + <ng-container *ngIf="!purchaseContext.offlinePaymentConfiguration?.showOnlyBasicInstructions"> + <ol class="mt-3"> + <ng-container *ngTemplateOutlet="basicInstruction"></ng-container> + <li [innerHTML]="'reservation-page-waiting.required-steps.2' | translate: {'0': purchaseContext.organizationEmail, '1': reservationInfo.id}"></li> + <li [innerHTML]="'reservation-page-waiting.required-steps.3' | translate"></li> + </ol> + </ng-container> + </ng-container> + + + <div class="text-center mt-5"> + <h4 [innerHTML]="'reservation-page-waiting.questions' | translate: {'0': purchaseContext.organizationEmail, '1': reservationInfo.id}"></h4> + </div> + + <div class="text-center text-muted mt-5">{{'reservation-page-complete.order-information' | translate: {'0': reservationId, '1': reservationInfo.firstName + ' ' + reservationInfo.lastName} }}</div> + <hr> + <div class="row d-flex justify-content-between mobile-add-margin-bottom"> + <div class="col-md-5 col-12 order-md-1" *ngIf="invoiceAvailable"> + <a [href]="'/api/v2/public/' + purchaseContextType + '/' + publicIdentifier + '/reservation/' + reservationInfo.id + '/invoice'" class="btn btn-success block-button" target="_blank" translate="reservation-page-complete.download-your-invoice"></a> + </div> + <div class="col-md-5 col-12 order-md-1 text-center" *ngIf="!purchaseContext.invoicingConfiguration.userCanDownloadReceiptOrInvoice"><p translate="reservation-page-waiting.invoice-will-be-sent"></p></div> + <div class="col-md-5 col-12 order-md-0"><a [href]="purchaseContext.websiteUrl" class="btn btn-light block-button" translate="to-event-site"></a></div> + </div> +</div> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.scss b/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.scss new file mode 100644 index 0000000000..bdfb00ce8c --- /dev/null +++ b/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.scss @@ -0,0 +1,4 @@ +.bank-account-owner-info { + padding-left: 1em; + margin-bottom: 0; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.ts b/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.ts new file mode 100644 index 0000000000..cfafb8b835 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/offline-payment/offline-payment.component.ts @@ -0,0 +1,78 @@ +import {Component, OnInit} from '@angular/core'; +import {ReservationService} from '../../shared/reservation.service'; +import {ReservationInfo} from '../../model/reservation-info'; +import {ActivatedRoute} from '@angular/router'; +import {zip} from 'rxjs'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../../shared/i18n.service'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {PurchaseContext} from '../../model/purchase-context'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {pollReservationStatus} from '../../shared/util'; + +@Component({ + selector: 'app-offline-payment', + templateUrl: './offline-payment.component.html', + styleUrls: ['./offline-payment.component.scss'] +}) +export class OfflinePaymentComponent implements OnInit { + + reservationInfo: ReservationInfo; + purchaseContextType: PurchaseContextType; + publicIdentifier: string; + reservationId: string; + paymentReason: string; + + purchaseContext: PurchaseContext; + + reservationFinalized: boolean; + + constructor( + private route: ActivatedRoute, + private reservationService: ReservationService, + public translate: TranslateService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private purchaseContextService: PurchaseContextService) { } + + public ngOnInit(): void { + + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.purchaseContextType = data.type; + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + zip( + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier), + this.reservationService.getReservationInfo(this.reservationId) + ).subscribe(([ev, reservationInfo]) => { + this.purchaseContext = ev; + this.reservationInfo = reservationInfo; + + this.paymentReason = `<mark>${this.reservationInfo.shortId}</mark>`; + + this.i18nService.setPageTitle('reservation-page-waiting.header.title', ev); + this.analytics.pageView(ev.analyticsConfiguration); + if (this.reservationInfo.status === 'OFFLINE_FINALIZING') { + this.reservationFinalized = false; + pollReservationStatus(this.reservationId, this.reservationService, res => { + if (res.status === 'DEFERRED_OFFLINE_PAYMENT') { + // redirect to deferred payment. Reload the page + location.reload(); + } + this.reservationInfo = res; + this.reservationFinalized = true; + }); + } else { + this.reservationFinalized = true; + } + }); + }); + } + + get invoiceAvailable(): boolean { + return this.reservationFinalized + && this.purchaseContext.invoicingConfiguration.userCanDownloadReceiptOrInvoice + && this.reservationInfo.invoiceNumber !== null; + } + +} diff --git a/frontend/projects/public/src/app/reservation/overview/overview.component.html b/frontend/projects/public/src/app/reservation/overview/overview.component.html new file mode 100644 index 0000000000..4752b96b23 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/overview/overview.component.html @@ -0,0 +1,143 @@ +<app-reservation> + +<div *ngIf="reservationInfo && purchaseContext"> + + <app-stepper [currentStep]="3" [free]="reservationInfo.orderSummary.free"></app-stepper> + + <app-countdown *ngIf="!expired" [validity]="reservationInfo.validity" (expired)="handleExpired($event)"></app-countdown> + + <form [formGroup]="overviewForm" (submit)="confirm()"> + + + <div class="page-header"> + <h2 translate="reservation-page.title"></h2> + </div> + + <div class="row"> + <div class="col-12" [ngClass]="{'col-md-7': !reservationInfo.orderSummary.free && purchaseContext.invoicingConfiguration.invoiceAllowed}"> + <app-summary-table [purchaseContext]="purchaseContext" [purchaseContextType]="purchaseContextType" [reservationInfo]="reservationInfo" [displayRemoveSubscription]="displayRemoveSubscription" (removeSubscription)="removeSubscription($event)"></app-summary-table> + </div> + <div class="d-none d-md-flex col-md-1 justify-content-md-center" *ngIf="purchaseContext.invoicingConfiguration.invoiceAllowed"> + <div class="border-left separator"></div> + </div> + + <div class="col-12 col-md-4 invoice-details" *ngIf="!reservationInfo.orderSummary.free && purchaseContext.invoicingConfiguration.invoiceAllowed"> + <div class="d-flex flex-column justify-content-center h-100"> + <div class="h5 d-none d-md-block" translate="reservation-page.invoice-details"></div> + <div class="page-header d-block d-md-none"><h2 translate="reservation-page.invoice-details"></h2></div> + <div class="row"> + <div class="col-12" *ngIf="reservationInfo.invoiceRequested"> + <address class="text-start"> + <div class="preformatted">{{reservationInfo.billingAddress}}</div> + <div *ngIf="hasTaxId">{{'invoice.vat' |translate: {'0': ('common.vat' | translate)} }} {{reservationInfo.billingDetails.taxId}}</div> + <hr *ngIf="enabledItalyEInvoicing"/> + <h5 class="d-none d-md-block" *ngIf="enabledItalyEInvoicing" translate="invoice-fields.addresseeItalyEInvoice"></h5> + <div *ngIf="enabledItalyEInvoicing">{{ taxIdMessageKey | translate}}{{': '}}{{italyEInvoicingFiscalCode}}</div> + <div *ngIf="enabledItalyEInvoicing && italyEInvoicingReference != null"> + {{(italyEInvoicingSelectedAddresseeKey | translate) + ': ' + italyEInvoicingReference}} + </div> + </address> + </div> + <div class="col-7 col-md-12 mt-md-3" *ngIf="!reservationInfo.invoiceRequested"> + <div class="h-100 d-flex flex-column justify-content-center"><span translate="reservation-page.no-invoice-requested"></span></div> + </div> + <div class="col-5 col-md-12 mt-md-3"> + <button type="button" class="btn btn-secondary" (click)="back(true)"><fa-icon [icon]="['far', 'edit']" a11yRole="presentation"></fa-icon>{{' '}}{{(reservationInfo.invoiceRequested ? 'common.edit' : 'reservation-page.request-invoice') | translate}}</button> + </div> + </div> + </div> + </div> + </div> + + <div *ngIf="purchaseContext.canApplySubscriptions && !appliedSubscription" class="mb-3"> + <h2 class="page-header mt-2" translate="reservation-page.subscription"></h2> + + <div *ngIf="!displaySubscriptionForm" class="row mt-2 mb-3"> + <div class="col-12 col-md-6"> + <p class="lead" translate="reservation-page.overview.pay-with-subscription"></p> + </div> + <div class="col-12 col-md-6"> + <button class="btn btn-default" (click)="toggleSubscriptionFormVisible()">{{'reservation-page.overview.apply-subscription-code' | translate}}</button> + </div> + </div> + + <div *ngIf="displaySubscriptionForm" class="mt-3 mb-3"> + <div class="row mb-2"> + <div class="col-12 col-md-6"> + <div class="form-group" [formGroup]="subscriptionCodeForm"> + <label for="subscription-input-code" class="sr-only">{{'reservation-page.overview.insert-subscription-code' | translate }}</label> + <input (keydown.enter)="applySubscription()" formControlName="subscriptionCode" required id="subscription-input-code" #subscriptionInput appInvalidFeedback class="form-control" [placeholder]="'reservation-page.overview.insert-subscription-code' | translate" autocomplete="off"> + <div class="invalid-feedback"></div> + </div> + </div> + <div class="col-12 col-md-6"> + <button class="btn btn-success" translate="reservation-page.overview.apply-subscription-code" type="button" (click)="applySubscription()"></button> + <button class="btn btn-light ms-2" translate="reservation-page-complete.cancel" type="button" (click)="toggleSubscriptionFormVisible()"></button> + </div> + </div> + </div> + </div> + + + <div *ngIf="!reservationInfo.orderSummary.free" class="page-header mt-2"><h2 translate="reservation-page.payment"></h2></div> + + <div class="alert alert-danger mt-3 mb-3" role="alert" *ngIf="globalErrors && globalErrors.length > 0"> + <div *ngFor="let err of globalErrors"><strong>{{err.code | translate: err.arguments}}</strong></div> + </div> + + <div class="alert mt-3 mb-3 text-center" role="alert" *ngIf="submitting" [class.alert-warning]="paymentStatusNotification && paymentStatusNotification.indeterminate"> + <div class="progress mb-3"> + <div class="progress-bar progress-bar-striped progress-bar-animated" [class.bg-warning]="paymentStatusNotification && paymentStatusNotification.indeterminate" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" style="width: 75%"></div> + </div> + <strong *ngIf="!paymentStatusNotification"><span translate="reservation.payment-in-progress" [attr.aria-hidden]="reservationInfo.orderSummary.free"></span><app-animated-dots aria-hidden="true"></app-animated-dots></strong> + <strong *ngIf="paymentStatusNotification"><span>{{ (paymentStatusNotification.indeterminate ? 'reservation.payment-processing.warning.message' : 'reservation.payment-processing.delay.message') | translate }}</span><app-animated-dots></app-animated-dots></strong> + <div class="text-center mt-3" *ngIf="paymentStatusNotification && !paymentStatusNotification.indeterminate"> + <button class="btn btn-default" translate="reservation.payment-processing.force-check" (click)="forceCheck()"></button> + </div> + </div> + + <div *ngIf="!reservationInfo.orderSummary.free" [class.invisible]="submitting"> + <app-payment-method-selector [purchaseContext]="purchaseContext" [reservationInfo]="reservationInfo" [overviewForm]="overviewForm" (selectedPaymentProvider)="registerCurrentPaymentProvider($event)"></app-payment-method-selector> + </div> + + + <div *ngIf="reservationInfo.orderSummary.free && purchaseContext.captchaConfiguration.captchaForOfflinePaymentAndFree" class="mt-2 wMarginBottom"> + <app-recaptcha [apiKey]="purchaseContext.captchaConfiguration.recaptchaApiKey" (recaptchaResponse)="handleRecaptchaResponse($event)"></app-recaptcha> + </div> + + <div class="form-check" *ngIf="purchaseContext.privacyPolicyUrl" [class.invisible]="submitting"> + <input class="form-check-input" id="privacyPolicyAccepted" formControlName="privacyPolicyAccepted" type="checkbox" aria-labelledby="privacy-policy-label">{{' '}} <!-- ugly, see https://github.com/angular/angular/issues/21049 --> + <label for="privacyPolicyAccepted" class="form-check-label" id="privacy-policy-label"> + <span [innerHTML]="'reservation-page.privacy.prefix' | translate"></span>{{' '}}<a [attr.href]="purchaseContext.privacyPolicyUrl" target="_blank" rel="noopener" translate="reservation-page.privacy.link.text"></a>{{' '}}<span translate="reservation-page.privacy.suffix"></span> + </label> + </div> + <div class="form-check mb-3" [class.invisible]="submitting"> + <input class="form-check-input" id="termsAndConditionsAccepted" formControlName="termAndConditionsAccepted" type="checkbox" aria-labelledby="terms-conditions-label"> + <label class="form-check-label" for="termsAndConditionsAccepted" id="terms-conditions-label"> + {{' '}}<span [innerHTML]="'reservation-page.tc.prefix' | translate"></span>{{' '}}<a [attr.href]="purchaseContext.termsAndConditionsUrl" target="_blank" rel="noopener" translate="reservation-page.tc.link.text"></a>{{' '}}<span translate="reservation-page.tc.suffix"></span> + </label> + </div> + + <hr class="mt-5"> + + <div class="row d-flex justify-content-between mobile-add-margin-bottom mt-4" *ngIf="!submitting"> + <div class="col-md-5 order-md-1 col-12"> + <button *ngIf="reservationInfo.orderSummary.free" type="submit" class="block-button btn btn-success" translate="reservation-page.continue" [disabled]="expired"></button> + <button *ngIf="!reservationInfo.orderSummary.free" type="submit" class="block-button btn btn-success" [disabled]="expired || !selectedPaymentProvider || !acceptedPrivacyAndTermAndConditions"> + <ng-container *ngIf="!reservationInfo.tokenAcquired"> + {{ (paymentMethodDeferred ? 'common.confirm' : 'reservation-page.pay')| translate}} <app-price-tag *ngIf="!paymentMethodDeferred" [purchaseContext]="purchaseContext" [formattedPrice]="reservationInfo.orderSummary.totalPrice" [displayTextInline]="true"></app-price-tag> + </ng-container> + <ng-container *ngIf="reservationInfo.tokenAcquired"> + {{'reservation-page.confirm-button' | translate}} + </ng-container> + </button> + </div> + <div class="col-md-5 order-md-0 col-12 "> + <button type="button" class="block-button btn btn-light" (click)="back()" translate="common.back" *ngIf="!reservationInfo.tokenAcquired"></button> + <button type="button" class="block-button btn btn-light" (click)="clearToken()" translate="reservation-page-complete.cancel" *ngIf="reservationInfo.tokenAcquired"></button> + </div> + </div> + </form> +</div> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/overview/overview.component.scss b/frontend/projects/public/src/app/reservation/overview/overview.component.scss new file mode 100644 index 0000000000..95564fc8e1 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/overview/overview.component.scss @@ -0,0 +1,17 @@ +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; + +@include media-breakpoint-up(md) { + .invoice-details { + padding: $table-cell-padding-x; + } + .invoice-details h5 { + font-weight: bold; + font-size: $font-size-base; + } +} + +div.separator { + height: 100%; +} diff --git a/frontend/projects/public/src/app/reservation/overview/overview.component.ts b/frontend/projects/public/src/app/reservation/overview/overview.component.ts new file mode 100644 index 0000000000..822d40aef8 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/overview/overview.component.ts @@ -0,0 +1,406 @@ +import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {ActivatedRoute, NavigationExtras, Router} from '@angular/router'; +import {ReservationService} from '../../shared/reservation.service'; +import {UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; +import {PaymentMethod, PaymentProxy, PaymentProxyWithParameters} from '../../model/event'; +import {ReservationInfo, SummaryRow} from '../../model/reservation-info'; +import {PaymentProvider, PaymentResult, PaymentStatusNotification, SimplePaymentProvider} from '../../payment/payment-provider'; +import {handleServerSideValidationError} from '../../shared/validation-helper'; +import {I18nService} from '../../shared/i18n.service'; +import {TranslateService} from '@ngx-translate/core'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {ErrorDescriptor, ValidatedResponse} from '../../model/validated-response'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ReservationExpiredComponent} from '../expired-notification/reservation-expired.component'; +import {zip} from 'rxjs'; +import {PurchaseContext} from '../../model/purchase-context'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {ModalRemoveSubscriptionComponent} from '../modal-remove-subscription/modal-remove-subscription.component'; +import {FeedbackService} from '../../shared/feedback/feedback.service'; +import {SearchParams} from '../../model/search-params'; +import {notifyPaymentErrorToParent} from '../../shared/util'; + +@Component({ + selector: 'app-overview', + templateUrl: './overview.component.html', + styleUrls: ['./overview.component.scss'] +}) +export class OverviewComponent implements OnInit { + + reservationInfo: ReservationInfo; + overviewForm: UntypedFormGroup; + globalErrors: ErrorDescriptor[]; + + private publicIdentifier: string; + private reservationId: string; + purchaseContext: PurchaseContext; + purchaseContextType: PurchaseContextType; + expired: boolean; + + submitting: boolean; + paymentStatusNotification: PaymentStatusNotification; + private forceCheckInProgress = false; + + selectedPaymentProvider: PaymentProvider; + + activePaymentMethods: {[key in PaymentMethod]?: PaymentProxyWithParameters}; + displaySubscriptionForm = false; + + @ViewChild('subscriptionInput') + subscriptionInput: ElementRef<HTMLInputElement>; + subscriptionCodeForm: UntypedFormGroup; + + constructor( + private route: ActivatedRoute, + private router: Router, + private reservationService: ReservationService, + private formBuilder: UntypedFormBuilder, + private i18nService: I18nService, + private translate: TranslateService, + private analytics: AnalyticsService, + private modalService: NgbModal, + private purchaseContextService: PurchaseContextService, + private feedbackService: FeedbackService) { } + + ngOnInit() { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + this.purchaseContextType = data.type; + + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier).subscribe(ev => { + this.purchaseContext = ev; + + this.i18nService.setPageTitle('reservation-page.header.title', ev); + + this.loadReservation(); + + this.analytics.pageView(ev.analyticsConfiguration); + }); + }); + + this.subscriptionCodeForm = this.formBuilder.group({ + subscriptionCode: this.formBuilder.control(null) + }); + } + + + loadReservation() { + this.reservationService.getReservationInfo(this.reservationId).subscribe(resInfo => { + this.reservationInfo = resInfo; + + this.activePaymentMethods = this.reservationInfo.activePaymentMethods; + let currentPaymentProxy: PaymentProxy = null; + let selectedPaymentMethod: PaymentMethod = null; + + if (!resInfo.orderSummary.free && this.paymentMethodsCount() === 1) { + selectedPaymentMethod = this.getSinglePaymentMethod(); + currentPaymentProxy = this.reservationInfo.activePaymentMethods[selectedPaymentMethod].paymentProxy; + } + + if (resInfo.orderSummary.free) { + selectedPaymentMethod = 'NONE'; + this.selectedPaymentProvider = new SimplePaymentProvider(); + } + + // + if (this.reservationInfo.tokenAcquired) { + currentPaymentProxy = this.reservationInfo.paymentProxy; + selectedPaymentMethod = this.getPaymentMethodMatchingProxy(currentPaymentProxy); + + // we override and keep only the one selected + const paymentProxyAndParam = this.reservationInfo.activePaymentMethods[selectedPaymentMethod]; + this.activePaymentMethods = {}; + this.activePaymentMethods[selectedPaymentMethod] = paymentProxyAndParam; + // + } else { + this.activePaymentMethods = this.reservationInfo.activePaymentMethods; + } + // + + this.overviewForm = this.formBuilder.group({ + termAndConditionsAccepted: this.reservationInfo.tokenAcquired, + privacyPolicyAccepted: this.reservationInfo.tokenAcquired, + selectedPaymentMethod: selectedPaymentMethod, + paymentProxy: currentPaymentProxy, + gatewayToken: null, + captcha: null + }); + }); + } + + paymentMethodsCount(): number { + return Object.keys(this.activePaymentMethods).length; + } + + private getPaymentMethodMatchingProxy(paymentProxy: PaymentProxy): PaymentMethod | null { + const keys: PaymentMethod[] = Object.keys(this.activePaymentMethods) as PaymentMethod[]; + for (const idx in keys) { + if (this.activePaymentMethods[keys[idx]].paymentProxy === paymentProxy) { + return keys[idx]; + } + } + return null; + } + + getSinglePaymentMethod(): PaymentMethod { + return (Object.keys(this.activePaymentMethods) as PaymentMethod[])[0]; + } + + back(requestInvoice?: boolean): void { + const extras: NavigationExtras = {}; + if (requestInvoice != null) { + extras.queryParams = { + 'requestInvoice': requestInvoice + }; + } + if (this.expired) { + this.reservationService.cancelPendingReservation(this.reservationId).subscribe(res => { + this.router.navigate([this.purchaseContextType, this.publicIdentifier]); + }); + } else { + this.reservationService.backToBooking(this.reservationId).subscribe(res => { + this.router.navigate([this.purchaseContextType, this.publicIdentifier, 'reservation', this.reservationId, 'book'], extras); + }); + } + } + + confirm() { + + if (!this.overviewForm.valid || this.selectedPaymentProvider == null) { + return; // prevent accidental submissions + } + + this.submitting = true; + this.registerUnloadHook(); + this.selectedPaymentProvider.statusNotifications().subscribe(notification => { + if (!this.forceCheckInProgress) { + this.paymentStatusNotification = notification; + } + }); + this.selectedPaymentProvider.pay().subscribe(paymentResult => { + if (paymentResult.success) { + this.overviewForm.get('gatewayToken').setValue(paymentResult.gatewayToken); + const overviewFormValue = this.overviewForm.value; + this.reservationService.confirmOverview(this.reservationId, overviewFormValue, this.translate.currentLang).subscribe(res => { + if (res.success) { + this.unregisterHook(); + if (res.value.redirect) { // handle the case of redirects (e.g. paypal, stripe) + window.location.href = res.value.redirectUrl; + } else { + this.router.navigate([this.purchaseContextType, this.publicIdentifier, 'reservation', this.reservationId, 'success'], { + queryParams: SearchParams.transformParams(this.route.snapshot.queryParams, this.route.snapshot.params) + }); + } + } else { + this.submitting = false; + this.unregisterHook(); + this.globalErrors = handleServerSideValidationError(res, this.overviewForm); + } + }, (err) => { + this.submitting = false; + this.unregisterHook(); + notifyPaymentErrorToParent(this.purchaseContext, this.reservationInfo, this.reservationId, err); + this.globalErrors = handleServerSideValidationError(err, this.overviewForm); + }); + } else { + console.log('paymentResult is not success (may be cancelled)'); + this.unregisterHook(); + if (paymentResult.reservationChanged) { + console.log('reservation status is changed. Trying to reload it...'); + // reload reservation, try to go to /success + this.router.navigate([this.purchaseContextType, this.publicIdentifier, 'reservation', this.reservationId, 'success']); + } else { + this.submitting = false; + } + } + }, (err) => { + this.submitting = false; + this.unregisterHook(); + this.notifyPaymentError(err); + notifyPaymentErrorToParent(this.purchaseContext, this.reservationInfo, this.reservationId, err); + }); + } + + forceCheck(): void { + this.paymentStatusNotification = null; + this.forceCheckInProgress = true; + this.reservationService.forcePaymentStatusCheck(this.reservationId).subscribe(res => { + if (res.success) { + console.log('reservation has been confirmed. Waiting for the PaymentProvider to acknowledge it...'); + } + }, err => { + console.log('error while force-checking', err); + notifyPaymentErrorToParent(this.purchaseContext, this.reservationInfo, this.reservationId, err); + }); + } + + private registerUnloadHook(): void { + window.addEventListener('beforeunload', onUnLoadListener); + console.log('warn on page reload: on'); + } + + private unregisterHook(): void { + window.removeEventListener('beforeunload', onUnLoadListener); + console.log('warn on page reload: off'); + } + + private notifyPaymentError(response: any): void { + const errorDescriptor = new ErrorDescriptor(); + errorDescriptor.fieldName = ''; + if (response != null && response instanceof PaymentResult) { + errorDescriptor.code = 'error.STEP_2_PAYMENT_PROCESSING_ERROR'; + errorDescriptor.arguments = { + '0': (<PaymentResult>response).reason + }; + } else { + errorDescriptor.code = 'error.STEP_2_PAYMENT_REQUEST_CREATION'; + } + const validatedResponse = new ValidatedResponse(); + validatedResponse.errorCount = 1; + validatedResponse.validationErrors = [errorDescriptor]; + this.globalErrors = handleServerSideValidationError(validatedResponse, this.overviewForm); + } + + get acceptedPrivacyAndTermAndConditions(): boolean { + if (this.purchaseContext.privacyPolicyUrl) { + return this.overviewForm.value.privacyPolicyAccepted && this.overviewForm.value.termAndConditionsAccepted; + } else { + return this.overviewForm.value.termAndConditionsAccepted; + } + } + + handleExpired(expired: boolean) { + setTimeout(() => { + if (!this.expired) { + this.expired = expired; + this.modalService.open(ReservationExpiredComponent, {centered: true, backdrop: 'static'}) + .result.then(() => this.router.navigate([this.purchaseContextType, this.publicIdentifier], {replaceUrl: true})); + } + }); + } + + registerCurrentPaymentProvider(paymentProvider: PaymentProvider) { + this.selectedPaymentProvider = paymentProvider; + } + + clearToken(): void { + this.reservationService.removePaymentToken(this.reservationId).subscribe(r => { + this.loadReservation(); + }); + } + + handleRecaptchaResponse(recaptchaValue: string) { + this.overviewForm.get('captcha').setValue(recaptchaValue); + } + + applySubscription() { + if (!this.subscriptionCodeForm.valid) { + return; + } + const control = this.subscriptionCodeForm.get('subscriptionCode'); + const subscriptionCode = control.value; + this.reservationService.applySubscriptionCode(this.reservationId, subscriptionCode, this.reservationInfo.email).subscribe(res => { + if (res.success) { + this.feedbackService.showSuccess('reservation-page.overview.applied-subscription-code'); + this.loadReservation(); + } else { + // set the first argument as the input + res.validationErrors.forEach(ed => { + ed.arguments = {'0': subscriptionCode}; + }); + control.setErrors({ serverError: res.validationErrors }); + } + }); + } + + removeSubscription(subscriptionRow: SummaryRow) { + this.modalService.open(ModalRemoveSubscriptionComponent, {centered: true, backdrop: 'static'}) + .result.then((res) => { + if (res) { + this.reservationService.removeSubscription(this.reservationId).subscribe(() => { + this.feedbackService.showInfo('reservation-page.overview.removed-subscription'); + this.loadReservation(); + }); + } + }); + } + + toggleSubscriptionFormVisible(): void { + this.displaySubscriptionForm = !this.displaySubscriptionForm; + if (this.displaySubscriptionForm) { + setTimeout(() => this.subscriptionInput.nativeElement.focus(), 200); + } else { + this.subscriptionInput.nativeElement.value = null; + } + } + + get enabledItalyEInvoicing(): boolean { + return this.purchaseContext.invoicingConfiguration.enabledItalyEInvoicing && + this.reservationInfo.billingDetails.invoicingAdditionalInfo.italianEInvoicing != null; + } + + get hasTaxId(): boolean { + return this.reservationInfo.invoiceRequested + && !this.reservationInfo.skipVatNr + && this.reservationInfo.billingDetails.taxId != null; + } + + get italyEInvoicingReference(): string { + const itEInvoicing = this.reservationInfo.billingDetails.invoicingAdditionalInfo.italianEInvoicing; + if (!this.enabledItalyEInvoicing || itEInvoicing == null) { + return ''; + } + return itEInvoicing.reference; + } + + get italyEInvoicingSelectedAddresseeKey(): string { + if (!this.enabledItalyEInvoicing) { + return ''; + } + const referenceType = this.reservationInfo.billingDetails.invoicingAdditionalInfo.italianEInvoicing.referenceType; + return referenceType === 'ADDRESSEE_CODE' ? 'invoice-fields.addressee-code' : 'invoice-fields.pec'; + } + + get italyEInvoicingFiscalCode(): string { + const itEInvoicing = this.reservationInfo.billingDetails.invoicingAdditionalInfo.italianEInvoicing; + if (!this.enabledItalyEInvoicing || itEInvoicing == null) { + return ''; + } + return itEInvoicing.fiscalCode; + } + + get paymentMethodDeferred(): boolean { + if (this.reservationInfo.tokenAcquired) { + return false; + } + return this.selectedPaymentProvider != null && this.selectedPaymentProvider.paymentMethodDeferred; + } + + get appliedSubscription(): boolean { + if (this.reservationInfo && this.reservationInfo.orderSummary && this.reservationInfo.orderSummary.summary) { + return this.reservationInfo.orderSummary.summary.filter((s) => s.type === 'APPLIED_SUBSCRIPTION').length > 0; + } + return false; + } + + get displayRemoveSubscription() { + return this.purchaseContextType === 'event'; + } + + + get taxIdMessageKey(): string { + if (this.reservationInfo.billingDetails.country === 'IT') { + return 'invoice-fields.fiscalCode'; + } + return 'invoice-fields.tax-id'; + } +} + +function onUnLoadListener(e: BeforeUnloadEvent) { + // Cancel the event + e.preventDefault(); + // Chrome requires returnValue to be set + e.returnValue = ''; +} diff --git a/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html new file mode 100644 index 0000000000..fa32646499 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html @@ -0,0 +1,71 @@ +<div class="row" [formGroup]="overviewForm"> + <div class="col-12" [class.col-md-5]="verticalLayout" *ngIf="activePaymentsCount > 1"> + <div *ngIf="verticalLayout" class="mb-md-4"> + <div *ngFor="let method of sortedAvailablePaymentMethods" class="form-check pt-3 pb-3" + [ngClass]="{ 'font-weight-bold border-top border-bottom': selectedPaymentMethod == method, 'text-muted border-right-md': selectedPaymentMethod != null && selectedPaymentMethod != method }"> + <input class="form-check-input" [attr.id]="method" type="radio" [value]="method" name="selectedPaymentMethod" formControlName="selectedPaymentMethod" role="radiogroup" [attr.aria-labelledby]="method + '-label'"> + <label class="form-check-label ms-2 w-100 h-100" [attr.for]="method" [id]="method + '-label'"><fa-icon [icon]="getPaymentMethodDetails(method).icon" a11yRole="presentation" class="ms-2 me-3"></fa-icon> {{ getPaymentMethodDetails(method).labelKey | translate }}</label> + </div> + </div> + <div class="row align-items-center mb-3" *ngIf="!verticalLayout"> + <div class="col-12 col-md-auto mb-2 mb-md-0 me-0 me-md-3" *ngFor="let method of sortedAvailablePaymentMethods" [class.active]="selectedPaymentMethod == method"> + <div class="form-check me-sm-2"> + <input class="form-check-input" [attr.id]="method" type="radio" [value]="method" name="selectedPaymentMethod" formControlName="selectedPaymentMethod" [attr.aria-labelledby]="method + '-label'"> + <label class="form-check-label ms-2 w-100 h-100" [attr.for]="method" [id]="method + '-label'" [ngClass]="{ 'font-weight-bold': selectedPaymentMethod == method, 'text-muted': selectedPaymentMethod != null && selectedPaymentMethod != method }"> + <fa-icon [icon]="getPaymentMethodDetails(method).icon" class="ms-2 me-3" a11yRole="presentation"></fa-icon> {{ getPaymentMethodDetails(method).labelKey | translate }} + </label> + </div> + </div> + </div> + </div> + <div class="col-12" *ngIf="activePaymentsCount == 1"> + <h4 class="mt-2"> + <fa-icon [icon]="getPaymentMethodDetails(selectedPaymentMethod).icon" class="ms-2 me-3" a11yRole="presentation"></fa-icon> {{ getPaymentMethodDetails(selectedPaymentMethod).labelKey | translate }} + </h4> + </div> + + <div class="col-12" [class.col-md-7]="verticalLayout" [class.mb-5]="!verticalLayout"> + <div *ngIf="!selectedPaymentMethod" class="text-muted" [ngClass]="{'h-100 text-center d-flex flex-md-column justify-content-center': verticalLayout }" translate="reservation-page.payment.select-method"></div> + <div *ngIf="selectedPaymentMethod" [ngClass]="{'h-100 d-flex flex-md-column justify-content-center': verticalLayout }"> + <app-offline-payment-proxy + [proxy]="selectedPaymentProxy" + [method]="selectedPaymentMethod" + [parameters]="activePaymentMethods[selectedPaymentMethod].parameters" + [overviewForm]="overviewForm" + (paymentProvider)="registerCurrentPaymentProvider($event)"> + </app-offline-payment-proxy> + <app-onsite-payment-proxy + [proxy]="selectedPaymentProxy" + [method]="selectedPaymentMethod" + [parameters]="activePaymentMethods[selectedPaymentMethod].parameters" + [overviewForm]="overviewForm" + (paymentProvider)="registerCurrentPaymentProvider($event)"> + </app-onsite-payment-proxy> + <app-stripe-payment-proxy + [purchaseContext]="purchaseContext" + [reservation]="reservationInfo" + [proxy]="selectedPaymentProxy" + [method]="selectedPaymentMethod" + [parameters]="activePaymentMethods[selectedPaymentMethod].parameters" + (paymentProvider)="registerCurrentPaymentProvider($event)"> + </app-stripe-payment-proxy> + <app-paypal-payment-proxy + [proxy]="selectedPaymentProxy" + [method]="selectedPaymentMethod" + [reservation]="reservationInfo" + (paymentProvider)="registerCurrentPaymentProvider($event)"> + </app-paypal-payment-proxy> + <app-mollie-payment-proxy + [proxy]="selectedPaymentProxy" + [method]="selectedPaymentMethod" + (paymentProvider)="registerCurrentPaymentProvider($event)"> + </app-mollie-payment-proxy> + <app-saferpay-payment-proxy + [proxy]="selectedPaymentProxy" + [method]="selectedPaymentMethod" + (paymentProvider)="registerCurrentPaymentProvider($event)"> + </app-saferpay-payment-proxy> + </div> + + </div> +</div> diff --git a/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.ts b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.ts new file mode 100644 index 0000000000..4fbf67e2ad --- /dev/null +++ b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.ts @@ -0,0 +1,68 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {PaymentMethod, PaymentMethodDetails, paymentMethodDetails, PaymentProxy, PaymentProxyWithParameters} from '../../model/event'; +import {PaymentProvider} from '../../payment/payment-provider'; +import {UntypedFormGroup} from '@angular/forms'; +import {ReservationInfo} from '../../model/reservation-info'; +import {PurchaseContext} from '../../model/purchase-context'; + +@Component({ + selector: 'app-payment-method-selector', + templateUrl: './payment-method-selector.component.html', + styleUrls: ['./payment-method-selector.scss'] +}) +export class PaymentMethodSelectorComponent implements OnInit { + + @Input() + purchaseContext: PurchaseContext; + @Input() + reservationInfo: ReservationInfo; + @Input() + overviewForm: UntypedFormGroup; + @Output() + selectedPaymentProvider = new EventEmitter<PaymentProvider>(); + + ngOnInit(): void { + this.overviewForm.get('selectedPaymentMethod').valueChanges.subscribe(v => { + // payment provider has changed, so we must reset the value hold by the container. + this.registerCurrentPaymentProvider(null); + const selectedMethod = this.reservationInfo.activePaymentMethods[v as PaymentMethod]; + this.overviewForm.get('paymentProxy').setValue(selectedMethod.paymentProxy); + }); + } + + get activePaymentMethods(): {[key in PaymentMethod]?: PaymentProxyWithParameters} { + return this.reservationInfo.activePaymentMethods; + } + + get sortedAvailablePaymentMethods(): PaymentMethod[] { + const activeKeys = Object.keys(this.activePaymentMethods); + return Object.keys(paymentMethodDetails) + .filter(pd => activeKeys.includes(pd)) + .map(pd => pd as PaymentMethod); + } + + get activePaymentsCount(): number { + return Object.keys(this.activePaymentMethods).length; + } + + get verticalLayout(): boolean { + return this.activePaymentsCount > 3; + } + + get selectedPaymentMethod(): PaymentMethod { + return this.overviewForm.get('selectedPaymentMethod').value as PaymentMethod; + } + + get selectedPaymentProxy(): PaymentProxy { + return this.overviewForm.get('paymentProxy').value as PaymentProxy; + } + + getPaymentMethodDetails(pm: PaymentMethod): PaymentMethodDetails { + return paymentMethodDetails[pm]; + } + + registerCurrentPaymentProvider(paymentProvider: PaymentProvider) { + this.selectedPaymentProvider.emit(paymentProvider); + } + +} diff --git a/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.scss b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.scss new file mode 100644 index 0000000000..99db74153c --- /dev/null +++ b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.scss @@ -0,0 +1,21 @@ +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; + +.payment-method-selector { + width: 100%; +} + +.payment-method-selector > label { + flex-basis: 100%; + padding:0; + padding-top:6px; + padding-bottom:6px; + font-size: 95%; +} + +@include media-breakpoint-up(md) { + .border-right-md { + border-right: $border-width solid $border-color !important; + } +} diff --git a/frontend/projects/public/src/app/reservation/processing-payment/processing-payment.component.html b/frontend/projects/public/src/app/reservation/processing-payment/processing-payment.component.html new file mode 100644 index 0000000000..735b5553bb --- /dev/null +++ b/frontend/projects/public/src/app/reservation/processing-payment/processing-payment.component.html @@ -0,0 +1,20 @@ +<app-reservation> + +<div *ngIf="reservationInfo && purchaseContext"> + + <app-stepper [currentStep]="3" [free]="reservationInfo.orderSummary.free" [inProgress]="true"></app-stepper> + + <div class="page-header"> + <h2 translate="reservation-page.title"></h2> + </div> + + <app-summary-table [purchaseContext]="purchaseContext" [reservationInfo]="reservationInfo"></app-summary-table> + + <div class="alert text-center" [ngClass]="{'alert-info': !providerWarningVisible, 'alert-warning': providerWarningVisible}"> + <div class="h5" *ngIf="!forceCheckVisible && !providerWarningVisible"><span translate="reservation.payment-in-progress"></span><app-animated-dots></app-animated-dots></div> + <div class="h5" *ngIf="forceCheckVisible || providerWarningVisible"><span>{{ (providerWarningVisible ? 'reservation.payment-processing.warning.message' : 'reservation.payment-processing.delay.message') | translate }}</span><app-animated-dots></app-animated-dots></div> + <div class="text-center" *ngIf="forceCheckVisible"><button class="btn btn-default ms-5" translate="reservation.payment-processing.force-check" (click)="forceCheck()"></button></div> + </div> +</div> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/processing-payment/processing-payment.component.ts b/frontend/projects/public/src/app/reservation/processing-payment/processing-payment.component.ts new file mode 100644 index 0000000000..37d1219c77 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/processing-payment/processing-payment.component.ts @@ -0,0 +1,102 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {ReservationInfo} from '../../model/reservation-info'; +import {ActivatedRoute, Router} from '@angular/router'; +import {ReservationService} from '../../shared/reservation.service'; +import {zip} from 'rxjs'; +import {I18nService} from '../../shared/i18n.service'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {PurchaseContext} from '../../model/purchase-context'; +import {SearchParams} from '../../model/search-params'; +import {notifyPaymentErrorToParent} from '../../shared/util'; + +@Component({ + selector: 'app-processing-payment', + templateUrl: './processing-payment.component.html' +}) +export class ProcessingPaymentComponent implements OnInit, OnDestroy { + + reservationInfo: ReservationInfo; + purchaseContext: PurchaseContext; + + private purchaseContextType: PurchaseContextType; + private publicIdentifier: string; + private reservationId: string; + forceCheckVisible = false; + providerWarningVisible = false; + private forceCheckInProgress = false; + + private intervalId: any; + + constructor( + private route: ActivatedRoute, + private router: Router, + private reservationService: ReservationService, + private purchaseContextService: PurchaseContextService, + private i18nService: I18nService, + private analytics: AnalyticsService + ) { } + + public ngOnInit(): void { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.purchaseContextType = data.type; + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + + zip( + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier), + this.reservationService.getReservationInfo(this.reservationId) + ).subscribe(([ev, reservationInfo]) => { + this.purchaseContext = ev; + this.reservationInfo = reservationInfo; + this.i18nService.setPageTitle('show-ticket.header.title', ev); + this.analytics.pageView(ev.analyticsConfiguration); + }); + + let checkCount = 0; + this.intervalId = setInterval(() => { + const currentStatus = this.reservationInfo.status; + this.reservationService.getReservationStatusInfo(this.reservationId).subscribe(res => { + checkCount++; + if (res.status !== currentStatus) { + clearInterval(this.intervalId); + this.reservationStateChanged(); + } + if ((!this.forceCheckVisible || checkCount > 120) && !this.forceCheckInProgress && checkCount % 10 === 0) { + this.providerWarningVisible = checkCount > 120; + this.forceCheckVisible = !this.providerWarningVisible; + } + }); + }, 2000); + }); + } + private reservationStateChanged() { + // try to navigate to /success. If the reservation is in a different status, the user will be + // redirected accordingly. + this.router.navigate([this.purchaseContextType, this.publicIdentifier, 'reservation', this.reservationId, 'success'], { + queryParams: SearchParams.transformParams(this.route.snapshot.queryParams, this.route.snapshot.queryParams) + }); + } + + public ngOnDestroy() { + clearInterval(this.intervalId); + } + + forceCheck(): void { + this.forceCheckVisible = false; + this.forceCheckInProgress = true; + this.reservationService.forcePaymentStatusCheck(this.reservationId).subscribe(status => { + if (status.redirect) { + window.location.href = status.redirectUrl; + } else if (status.success || status.failure) { + this.reservationStateChanged(); + } + this.forceCheckInProgress = false; + }, err => { + console.log('got error', err); + notifyPaymentErrorToParent(this.purchaseContext, this.reservationInfo, this.reservationId, err); + }); + } + + +} diff --git a/frontend/projects/public/src/app/reservation/release-ticket/release-ticket.component.ts b/frontend/projects/public/src/app/reservation/release-ticket/release-ticket.component.ts new file mode 100644 index 0000000000..a762cf68a9 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/release-ticket/release-ticket.component.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-release-ticket', + templateUrl: './release-ticket.html' +}) +export class ReleaseTicketComponent { + constructor(public activeModal: NgbActiveModal) { + } +} diff --git a/frontend/projects/public/src/app/reservation/release-ticket/release-ticket.html b/frontend/projects/public/src/app/reservation/release-ticket/release-ticket.html new file mode 100644 index 0000000000..33cfbd1e25 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/release-ticket/release-ticket.html @@ -0,0 +1,12 @@ +<div class="modal-body text-center"> + <h4 class="modal-title text-danger text-center"><fa-icon [icon]="['fas', 'exclamation-triangle']" a11yRole="presentation"></fa-icon>{{' '}}{{'reservation-page-complete.confirm-cancellation.text' | translate}}</h4> + <hr/> + <div class="row"> + <div class="col-6"> + <button type="button" class="btn btn-light block-button" (click)="activeModal.close('no')" translate="reservation-page-complete.confirm-cancellation.button.no"></button> + </div> + <div class="col-6"> + <button type="button" class="btn btn-danger block-button" (click)="activeModal.close('yes')" translate="reservation-page-complete.confirm-cancellation.button.yes"></button> + </div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/reservation.component.html b/frontend/projects/public/src/app/reservation/reservation.component.html new file mode 100644 index 0000000000..864f5743c0 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/reservation.component.html @@ -0,0 +1,8 @@ +<app-purchase-context-container [purchaseContext]="purchaseContext" [displayFooterLinks]="displayFooterLinks"> + <div *ngIf="purchaseContext" class="add-margin-bottom"> + <header><app-purchase-context-header [purchaseContext]="purchaseContext" [type]="type" + [displayTopLoginButton]="enableLoginButton"></app-purchase-context-header></header> + <hr> + <main><ng-content></ng-content></main> + </div> +</app-purchase-context-container> diff --git a/frontend/projects/public/src/app/reservation/reservation.component.ts b/frontend/projects/public/src/app/reservation/reservation.component.ts new file mode 100644 index 0000000000..7702151731 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/reservation.component.ts @@ -0,0 +1,40 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {PurchaseContextService, PurchaseContextType} from '../shared/purchase-context.service'; +import {PurchaseContext} from '../model/purchase-context'; +import {Subscription, zip} from 'rxjs'; + +@Component({ + selector: 'app-reservation', + templateUrl: './reservation.component.html' +}) +export class ReservationComponent implements OnInit, OnDestroy { + + private routerSubscription?: Subscription; + purchaseContext: PurchaseContext; + type: PurchaseContextType; + enableLoginButton = false; + displayFooterLinks = false; + + constructor( + private route: ActivatedRoute, + private purchaseContextService: PurchaseContextService) { } + + ngOnInit() { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.purchaseContextService.getContext(data['type'], params[data['publicIdentifierParameter']]).subscribe(purchaseContext => { + this.type = data['type']; + this.purchaseContext = purchaseContext; + }); + }); + this.routerSubscription = this.route.url.subscribe(url => { + const path = url[url.length - 1].path; + this.enableLoginButton = path === 'success'; + this.displayFooterLinks = path === 'book'; + }); + } + + ngOnDestroy(): void { + this.routerSubscription?.unsubscribe(); + } +} diff --git a/frontend/projects/public/src/app/reservation/reservation.guard.ts b/frontend/projects/public/src/app/reservation/reservation.guard.ts new file mode 100644 index 0000000000..c0439d21d3 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/reservation.guard.ts @@ -0,0 +1,80 @@ +import {Injectable} from '@angular/core'; +import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; +import {Observable, of} from 'rxjs'; +import {catchError, map} from 'rxjs/operators'; +import {ReservationService} from '../shared/reservation.service'; +import {ReservationStatus} from '../model/reservation-info'; +import {SuccessComponent} from './success/success.component'; +import {OverviewComponent} from './overview/overview.component'; +import {BookingComponent} from './booking/booking.component'; +import {OfflinePaymentComponent} from './offline-payment/offline-payment.component'; +import {ProcessingPaymentComponent} from './processing-payment/processing-payment.component'; +import {NotFoundComponent} from './not-found/not-found.component'; +import {ErrorComponent} from './error/error.component'; +import {DeferredOfflinePaymentComponent} from './deferred-offline-payment/deferred-offline-payment.component'; +import {PurchaseContextType} from '../shared/purchase-context.service'; +import {SuccessSubscriptionComponent} from './success-subscription/success-subscription.component'; + +@Injectable({ + providedIn: 'root' +}) +export class ReservationGuard implements CanActivate { + + constructor(private reservationService: ReservationService, private router: Router) { + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | boolean { + return this.checkAndRedirect(route.data['type'], route.params[route.data['publicIdentifierParameter']], route.params['reservationId'], route.component); + } + + private checkAndRedirect(type: PurchaseContextType, publicIdentifier: string, reservationId: string, component: any): Observable<boolean | UrlTree> { + return this.reservationService.getReservationStatusInfo(reservationId) + .pipe(catchError(err => of({ status: <ReservationStatus>'NOT_FOUND', validatedBookingInformation: false })), map(reservation => { + const selectedComponent = getCorrespondingController(type, reservation.status, reservation.validatedBookingInformation); + if (component === selectedComponent) { + return true; + } + return this.router.createUrlTree(getRouteFromComponent(selectedComponent, type, publicIdentifier, reservationId)); + })); + } +} + +function getRouteFromComponent(component: any, type: PurchaseContextType, publicIdentifier: string, reservationId: string): string[] { + if (component === OverviewComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'overview']; + } else if (component === BookingComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'book']; + } else if (component === SuccessComponent || component === SuccessSubscriptionComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'success']; + } else if (component === OfflinePaymentComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'waiting-payment']; + } else if (component === DeferredOfflinePaymentComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'deferred-payment']; + } else if (component === ProcessingPaymentComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'processing-payment']; + } else if (component === NotFoundComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'not-found']; + } else if (component === ErrorComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'error']; + } else { + return ['/']; + } +} + +function getCorrespondingController(type: PurchaseContextType, status: ReservationStatus, validatedBookingInformations: boolean) { + switch (status) { + case 'PENDING': return validatedBookingInformations ? OverviewComponent : BookingComponent; + case 'COMPLETE': + case 'FINALIZING': + return type === 'subscription' ? SuccessSubscriptionComponent : SuccessComponent; + case 'OFFLINE_PAYMENT': + case 'OFFLINE_FINALIZING': + return OfflinePaymentComponent; + case 'DEFERRED_OFFLINE_PAYMENT': return DeferredOfflinePaymentComponent; + case 'EXTERNAL_PROCESSING_PAYMENT': + case 'WAITING_EXTERNAL_CONFIRMATION': return ProcessingPaymentComponent; + case 'IN_PAYMENT': + case 'STUCK': return ErrorComponent; + default: return NotFoundComponent; + } +} diff --git a/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.html b/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.html new file mode 100644 index 0000000000..e29bc7fd50 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.html @@ -0,0 +1,102 @@ +<app-reservation> + <div *ngIf="reservationInfo && purchaseContext"> + <div class="alert alert-success mb-5"> + <div class="row"> + <div class="col-sm-12"> + <div class="h3" *ngIf="reservationFinalized" [innerHTML]="'reservation-page-complete.info-subscription' | translate"></div> + <div class="h3" *ngIf="!reservationFinalized" translate="reservation-page-complete.reservation.finalization-in-progress"></div> + </div> + </div> + <div class="row mt-2" *ngIf="reservationFinalized"> + <div class="col-sm-12" [innerHTML]="'reservation-page-complete.info-subscription-email' | translate: {'0':reservationInfo.email}"></div> + </div> + <div class="row mt-3"> + <div class="col-12 col-md-5 mb-1 mb-md-0" *ngIf="reservationFinalized" [class.offset-md-2]="downloadBillingDocumentVisible" [class.offset-md-7]="!downloadBillingDocumentVisible"> + <button type="button" (click)="reSendReservationEmail()" class="btn btn-default block-button"><fa-icon [icon]="['far', 'envelope']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.resend-reservation-email'|translate}}</button> + </div> + <div class="col-12 col-md-5 mb-md-0" *ngIf="downloadBillingDocumentVisible"> + <ng-container> + {{' '}} + <a *ngIf="reservationInfo.invoiceNumber !== null" + class="btn btn-default block-button" + [href]="'/api/v2/public/event/'+purchaseContext.shortName+'/reservation/'+reservationInfo.id+'/invoice'" + target="_blank"> + <fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-your-invoice'|translate}} + </a> + <a *ngIf="reservationInfo.invoiceNumber === null" + class="btn btn-default block-button" + [href]="'/api/v2/public/event/'+purchaseContext.shortName+'/reservation/'+reservationInfo.id+'/receipt'" + target="_blank"> + <fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-your-receipt'|translate}} + </a> + </ng-container> + </div> + </div> + </div> + + <h1 translate="reservation-page-complete.subscription"></h1> + + <div class="row mt-5"> + <div class="col-12 col-lg-6"> + <app-basic-subscription-info [cardLayout]="false" [subscription]="subscription" [owner]="subscriptionInfo.owner"></app-basic-subscription-info> + <p class="lead"> + <span *ngIf="subscriptionInfo.usageDetails.used > 0">{{ 'reservation-page-complete.subscription.usage' | translate:{'0': subscriptionInfo.usageDetails.used} }}</span> + <span *ngIf="subscriptionInfo.usageDetails.available"> {{ 'reservation-page-complete.subscription.usage.available' | translate: {'0': subscriptionInfo.usageDetails.available} }}</span> + </p> + </div> + <div class="col-12 col-lg-6 text-center d-lg-flex flex-lg-column justify-content-lg-between" *ngIf="displayPin"> + <div class="mt-5 mt-lg-0"> + <p class="lead" translate="reservation-page-complete.subscription.pin-description"></p> + </div> + <div class="h3 text-center text-success"> + {{subscriptionInfo.pin}} + <button class="btn btn-light btn-xs" [ngbTooltip]="'reservation-page-complete.subscription.copy-pin'|translate" + type="button" [appClipboardCopy]="subscriptionInfo.pin" (copied)="copied($event)"> + <fa-icon [icon]="['far', 'copy']" a11yRole="presentation"></fa-icon> + <span class="sr-only" translate="reservation-page-complete.subscription.copy-pin"></span> + </button> + </div> + <div class="mt-5 mt-lg-0"> + <p class="lead" translate="reservation-page-complete.subscription.id-description"></p> + <div class="h6 text-center"> + {{subscriptionInfo.id}} + <button class="btn btn-light btn-xs" [ngbTooltip]="'reservation-page-complete.subscription.copy-id'|translate" + type="button" [appClipboardCopy]="subscriptionInfo.id" (copied)="copied($event)"> + <fa-icon [icon]="['far', 'copy']" a11yRole="presentation"></fa-icon> + <span class="sr-only" translate="reservation-page-complete.subscription.copy-id"></span> + </button> + </div> + </div> + </div> + </div> + + <ng-container *ngIf="showReservationButtons"> + <hr class="mt-5"> + + <div class="mt-5" *ngIf="compatibleEvents.length > 0"> + <div class="h3">{{ 'reservation-page-complete.subscription.buy-tickets' | translate }}</div> + <div class="card-deck mt-5"> + <app-basic-event-info [event]="event" class="mb-4" *ngFor="let event of compatibleEvents" [params]="{subscription: subscriptionInfo.id}"></app-basic-event-info> + </div> + </div> + + <hr class="mt-5" *ngIf="compatibleEvents.length > 0"> + + <div class="mt-5"> + <div class="row d-flex justify-content-md-between"> + <div class="col-md-5 order-md-1 col-12 mb-2"> + <a class="block-button btn btn-success" [routerLink]="['/events-all']" [queryParams]="{ subscription: subscriptionInfo.id }" translate="reservation-page-complete.buy-tickets"></a> + </div> + <div class="col-md-5 order-md-0 col-12 mt-2 mt-md-0 mb-2"> + <a class="block-button btn btn-light" [routerLink]="['/']" translate="to-home"></a> + </div> + </div> + </div> + <div class="text-center text-muted mt-5">{{'reservation-page-complete.order-information' | translate: {'0': reservationId, '1': reservationInfo.firstName + ' ' + reservationInfo.lastName} }}</div> + <div class="text-center mt-1"> + <a href="https://alf.io" [attr.title]="'alfio.credits' | translate" target="_blank" rel="noreferrer noopener" translate="alfio.credits"></a> + </div> + </ng-container> + </div> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.scss b/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.ts b/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.ts new file mode 100644 index 0000000000..7acb422e92 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/success-subscription/success-subscription.component.ts @@ -0,0 +1,134 @@ +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {zip} from 'rxjs'; +import {PurchaseContext} from '../../model/purchase-context'; +import {ReservationInfo, ReservationSubscriptionInfo} from '../../model/reservation-info'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {I18nService} from '../../shared/i18n.service'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {ReservationService} from '../../shared/reservation.service'; +import {TranslateService} from '@ngx-translate/core'; +import {SubscriptionInfo} from '../../model/subscription'; +import {FeedbackService} from '../../shared/feedback/feedback.service'; +import {EventService} from '../../shared/event.service'; +import {BasicEventInfo} from '../../model/basic-event-info'; +import {SearchParams} from '../../model/search-params'; +import {ReservationStatusChanged} from '../../model/embedding-configuration'; +import {embedded, pollReservationStatus} from '../../shared/util'; + +@Component({ + selector: 'app-success-subscription', + templateUrl: './success-subscription.component.html', + styleUrls: ['./success-subscription.component.scss'] +}) +export class SuccessSubscriptionComponent implements OnInit { + + private publicIdentifier: string; + reservationId: string; + private purchaseContextType: PurchaseContextType; + purchaseContext: PurchaseContext; + reservationInfo: ReservationInfo; + compatibleEvents: Array<BasicEventInfo> = []; + + reservationFinalized = true; + invoiceReceiptReady = true; + + constructor( + private route: ActivatedRoute, + private purchaseContextService: PurchaseContextService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private reservationService: ReservationService, + private translateService: TranslateService, + private feedbackService: FeedbackService, + private eventService: EventService + ) { } + + ngOnInit(): void { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.publicIdentifier = params[data.publicIdentifierParameter]; + this.reservationId = params['reservationId']; + this.purchaseContextType = data.type; + + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier).subscribe(ev => { + this.purchaseContext = ev; + this.i18nService.setPageTitle('reservation-page.header.title', ev); + this.loadReservation(); + this.analytics.pageView(ev.analyticsConfiguration); + }); + }); + } + + private loadReservation() { + this.reservationService.getReservationInfo(this.reservationId).subscribe(resInfo => { + this.processReservationInfo(resInfo); + if (!this.reservationFinalized) { + pollReservationStatus(this.reservationId, this.reservationService, res1 => this.processReservationInfo(res1)); + } + }); + } + + private processReservationInfo(resInfo: ReservationInfo) { + const embeddingEnabled = this.purchaseContext.embeddingConfiguration.enabled; + if (embedded && embeddingEnabled) { + window.parent.postMessage( + new ReservationStatusChanged(resInfo.status, this.reservationId), + this.purchaseContext.embeddingConfiguration.notificationOrigin + ); + } + this.reservationInfo = resInfo; + this.reservationFinalized = resInfo.status !== 'FINALIZING'; + this.invoiceReceiptReady = resInfo.metadata.readyForConfirmation; + if (this.reservationFinalized && (!embedded || !embeddingEnabled)) { + this.loadCompatibleEvents(this.subscriptionInfo.id); + } + } + + private loadCompatibleEvents(subscriptionId: string): void { + this.eventService.getEvents(new SearchParams(subscriptionId, null, null, null)).subscribe(events => { + this.compatibleEvents = events; + }); + } + + get purchaseContextTitle(): string { + return this.purchaseContext.title[this.translateService.currentLang]; + } + + get downloadBillingDocumentVisible(): boolean { + return this.purchaseContext.invoicingConfiguration.userCanDownloadReceiptOrInvoice + && this.reservationInfo.paid + && this.reservationInfo.invoiceOrReceiptDocumentPresent; + } + + get subscriptionInfo(): ReservationSubscriptionInfo { + return this.reservationInfo.subscriptionInfos[0]; + } + + get displayPin(): boolean { + if (this.subscriptionInfo.configuration != null) { + return this.reservationFinalized && this.subscriptionInfo.configuration.displayPin; + } + return this.reservationFinalized; // by default, we display PIN + } + + public reSendReservationEmail(): void { + this.reservationService.reSendReservationEmail('subscription', this.publicIdentifier, this.reservationId, this.i18nService.getCurrentLang()).subscribe(() => { + this.feedbackService.showSuccess('email.confirmation-email-sent'); + }); + } + + get subscription(): SubscriptionInfo { + return this.purchaseContext as SubscriptionInfo; + } + + public copied(payload: string): void { + this.feedbackService.showSuccess('reservation-page-complete.subscription.copy.success'); + } + + get showReservationButtons(): boolean { + return this.reservationFinalized + && (!embedded || !this.purchaseContext.embeddingConfiguration.enabled) + && !this.reservationInfo.metadata.hideConfirmationButtons; + } + +} diff --git a/frontend/projects/public/src/app/reservation/success/success.component.html b/frontend/projects/public/src/app/reservation/success/success.component.html new file mode 100644 index 0000000000..71572b7229 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/success/success.component.html @@ -0,0 +1,118 @@ +<app-reservation> + +<div class="alert alert-success alert-dismissible" role="alert" *ngIf="reservationMailSent"> + <button type="button" class="close" (click)="reservationMailSent = false"><span aria-hidden="true">×</span><span class="sr-only" translate="reservation-page-complete.cancel"></span></button> + <strong translate="email.confirmation-email-sent"></strong> +</div> + + +<div *ngIf="reservationInfo && event"> + <div class="alert alert-success mb-5"> + <div class="row"> + <div class="col-sm-12"> + <div class="h3" *ngIf="reservationFinalized" [innerHTML]="'reservation-page-complete.info-assign' | translate: { '0': purchaseContextTitle }"></div> + <div class="h3" *ngIf="!reservationFinalized" translate="reservation-page-complete.reservation.finalization-in-progress"></div> + </div> + </div> + <div class="row mt-2" *ngIf="reservationFinalized"> + <div class="col-sm-12" [innerHTML]="'reservation-page-complete.info-assign-email' | translate: {'0':reservationInfo.email}"></div> + </div> + <div class="row mt-3"> + <div class="col-12 col-md-5 mb-1 mb-md-0" *ngIf="reservationFinalized" [class.offset-md-2]="downloadBillingDocumentVisible" [class.offset-md-7]="!downloadBillingDocumentVisible"> + <button type="button" (click)="reSendReservationEmail()" class="btn btn-default block-button"><fa-icon [icon]="['far', 'envelope']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.resend-reservation-email'|translate}}</button> + </div> + <div class="col-12 col-md-5 mb-md-0" *ngIf="downloadBillingDocumentVisible"> + <ng-container> + {{' '}} + <a *ngIf="reservationInfo.invoiceNumber !== null" + class="btn btn-default block-button" + [href]="'/api/v2/public/event/'+event.shortName+'/reservation/'+reservationInfo.id+'/invoice'" + target="_blank"> + <fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-your-invoice'|translate}} + </a> + <a *ngIf="reservationInfo.invoiceNumber === null" + class="btn btn-default block-button" + [href]="'/api/v2/public/event/'+event.shortName+'/reservation/'+reservationInfo.id+'/receipt'" + target="_blank"> + <fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-your-receipt'|translate}} + </a> + </ng-container> + </div> + </div> + </div> + + <h2 translate="reservation-page-complete.your-tickets"></h2> + <div *ngFor="let tc of reservationInfo.ticketsByCategory"> + + <div class="card mt-4" *ngFor="let ticket of tc.tickets" [ngClass]="{'border-left-warning': !ticket.assigned, 'shadow': ticketsFormShow[ticket.uuid], 'border-left-success': ticket.assigned && !ticketsFormShow[ticket.uuid]}"> + <div class="card-body"> + <div class="attendees-data d-md-flex w-100 justify-content-between"> + <h3 class="card-title flex-md-shrink-1"><fa-icon [icon]="['fas', ticket.assigned ? 'check' : 'exclamation-triangle']" size="xs" [classes]="[ ticket.assigned ? 'text-success' : 'text-warning', 'd-none', 'd-md-inline']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.ticket-nr' | translate}}<span class="ticket-counter"></span> <small class="ms-2 text-muted">- {{tc.name}}</small></h3> + <h3 class="card-title flex-md-shrink-0" *ngIf="ticket.assigned">{{ticket.fullName}}</h3> + <h3 class="card-title text-muted" *ngIf="!ticket.assigned">{{'reservation-page-complete.ticket-not-assigned' | translate }}</h3> + </div> + <ng-container *ngIf="reservationFinalized"> + <hr *ngIf="!ticketFormVisible"> + <div class="row justify-content-end"> + <div class="col-lg-3 col-sm-6 col-12 mb-2" *ngIf="!isOnlineTicket(tc) && ticket.assigned && !ticket.cancellationEnabled && ticket.downloadEnabled" > + <a [routerLink]="['/event', event.shortName, 'ticket', ticket.uuid, 'view']" target="_blank" class="btn btn-default block-button"><fa-icon [icon]="['fas', 'search-plus']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.show-ticket'|translate}}</a> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" *ngIf="!isOnlineTicket(tc) && ticket.assigned && ticket.downloadEnabled"> + <button *ngIf="walletIntegrationEnabled" type="button" class="btn btn-default btn-block" (click)="downloadTicket(ticket)"><fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-ticket'|translate}}</button> + <a *ngIf="!walletIntegrationEnabled" [attr.href]="'/api/v2/public/event/' + event.shortName + '/ticket/' + ticket.uuid + '/download-ticket'" class="btn btn-default block-button"><fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-ticket'|translate}}</a> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" *ngIf="ticket.assigned && ticket.sendMailEnabled"> + <button type="button" class="btn btn-default block-button send-ticket-by-email" (click)="sendEmailForTicket(ticket.uuid)"><fa-icon [icon]="['far', 'envelope']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.send-ticket-by-email-to'|translate}}</button> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" [ngClass]="{'offset-lg-9 offset-sm-6': !ticket.assigned}"> + <button type="button" class="btn btn-default block-button update-ticket-owner" (click)="ticketsFormShow[ticket.uuid] = true"><fa-icon [icon]="ticket.assigned ? ['far', 'edit'] : ['fas', 'check']" a11yRole="presentation"></fa-icon> {{(ticket.assigned ? 'reservation-page-complete.update-ticket-owner' : 'reservation-page-complete.assign') |translate}}</button> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" *ngIf="ticket.assigned && ticket.cancellationEnabled"> + <button type="button" class="btn btn-danger block-button" (click)="releaseTicket(ticket)"><fa-icon [icon]="['fas', 'eraser']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.release-button.text'|translate}}</button> + </div> + </div> + </ng-container> + + <div *ngIf="ticketsFormShow[ticket.uuid]" class="mt-4"> + <div class="alert alert-info" translate="reservation-page-complete.info-update" *ngIf="event.assignmentConfiguration.enableTicketTransfer && ticket.assigned && !ticket.locked"></div> + <form [formGroup]="ticketsFormControl[ticket.uuid]" (submit)="updateTicket(ticket.uuid)"> + <app-ticket-form [ticket]="ticket" [purchaseContext]="event" [form]="ticketsFormControl[ticket.uuid]" [reservationMetadata]="reservationInfo.metadata"></app-ticket-form> + <div class="bg-white pb-3 pt-3" [class.sticky]="ticket.ticketFieldConfigurationAfterStandard.length > 0"> + <div class="row d-flex justify-content-between"> + <div class="col-md-5 order-md-1 col-12"> + <button type="submit" class="btn btn-success block-button">{{ (ticket.assigned ? 'reservation-page-complete.update' : 'reservation-page-complete.assign') | translate}}</button> + </div> + <div class="col-md-5 mt-2 mt-md-0 order-md-0 col-12"> + <button type="button" (click)="hideTicketForm(ticket.uuid)" translate="reservation-page-complete.cancel" class="btn btn-light block-button"></button> + </div> + </div> + </div> + </form> + </div> + + <div class="alert alert-success mt-2 mb-2 hidden alert-dismissible" role="alert" *ngIf="sendEmailForTicketStatus[ticket.uuid]"> + <button type="button" class="btn-close" (click)="sendEmailForTicketStatus[ticket.uuid] = false"><span class="sr-only" translate="reservation-page-complete.cancel"></span></button> + <strong translate="email.ticket-email-sent"></strong> + </div> + </div> + </div> + </div> + + <div *ngIf="!ticketFormVisible && showReservationButtons" class="mt-5"> + <hr> + <div class="row d-flex justify-content-between"> + <div class="col-md-5 order-md-1 col-12 mb-2"> + <a class="block-button btn btn-success" [routerLink]="['/event', event.shortName]" translate="buy-other-tickets"></a> + </div> + <div class="col-md-5 order-md-0 col-12 mt-2 mt-md-0 mb-2"> + <a class="block-button btn btn-light" [attr.href]="event.websiteUrl" translate="to-event-site"></a> + </div> + </div> + <div class="text-center text-muted mt-3">{{'reservation-page-complete.order-information' | translate: {'0': reservationId, '1': reservationInfo.firstName + ' ' + reservationInfo.lastName} }}</div> + <div class="text-center mt-1"> + <a href="https://alf.io" [attr.title]="'alfio.credits' | translate" target="_blank" rel="noreferrer noopener" translate="alfio.credits"></a> + </div> + </div> +</div> + +</app-reservation> diff --git a/frontend/projects/public/src/app/reservation/success/success.component.scss b/frontend/projects/public/src/app/reservation/success/success.component.scss new file mode 100644 index 0000000000..3cad39009b --- /dev/null +++ b/frontend/projects/public/src/app/reservation/success/success.component.scss @@ -0,0 +1,19 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; + +p.ticket-info { + padding: 15px; + margin-top: 10px; +} + +.bg-info { + background-color: #d9edf7!important; +} + +.sticky { + position: -webkit-sticky; + position: sticky; + bottom: 0px; + left: 0px; + z-index: 200; +} diff --git a/frontend/projects/public/src/app/reservation/success/success.component.ts b/frontend/projects/public/src/app/reservation/success/success.component.ts new file mode 100644 index 0000000000..54d914959c --- /dev/null +++ b/frontend/projects/public/src/app/reservation/success/success.component.ts @@ -0,0 +1,188 @@ +import {Component, OnInit} from '@angular/core'; +import {ReservationService} from '../../shared/reservation.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {Event} from '../../model/event'; +import {EventService} from '../../shared/event.service'; +import {TicketService} from '../../shared/ticket.service'; +import {Ticket} from '../../model/ticket'; +import {ReservationInfo, TicketsByTicketCategory} from '../../model/reservation-info'; +import {I18nService} from '../../shared/i18n.service'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {handleServerSideValidationError} from '../../shared/validation-helper'; +import {UntypedFormGroup} from '@angular/forms'; +import {TranslateService} from '@ngx-translate/core'; +import {InfoService} from '../../shared/info.service'; +import {first} from 'rxjs/operators'; +import {WalletConfiguration} from '../../model/info'; +import {ReservationStatusChanged} from '../../model/embedding-configuration'; +import {embedded, pollReservationStatus} from '../../shared/util'; + +@Component({ + selector: 'app-success', + templateUrl: './success.component.html', + styleUrls: ['./success.component.scss'] +}) +export class SuccessComponent implements OnInit { + + reservationInfo: ReservationInfo; + eventShortName: string; + reservationId: string; + + event: Event; + + reservationMailSent = false; + sendEmailForTicketStatus: {[key: string]: boolean} = {}; + ticketsFormControl: {[key: string]: UntypedFormGroup} = {}; + ticketsFormShow: {[key: string]: boolean} = {}; + ticketsReleaseShow: {[key: string]: boolean} = {}; + + unlockedTicketCount = 0; + ticketsAllAssigned = true; + reservationFinalized = true; + invoiceReceiptReady = true; + private walletConfiguration: WalletConfiguration; + + constructor( + private route: ActivatedRoute, + private reservationService: ReservationService, + private eventService: EventService, + private ticketService: TicketService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private router: Router, + private translateService: TranslateService, + private infoService: InfoService) { } + + public ngOnInit(): void { + this.route.params.subscribe(params => { + this.eventShortName = params['eventShortName']; + this.reservationId = params['reservationId']; + this.infoService.getInfo().pipe(first()) + .subscribe(info => this.walletConfiguration = info.walletConfiguration); + this.eventService.getEvent(this.eventShortName).subscribe(ev => { + this.event = ev; + this.i18nService.setPageTitle('reservation-page-complete.header.title', ev); + this.analytics.pageView(ev.analyticsConfiguration); + }); + this.loadReservation(); + }); + } + + private loadReservation(): void { + this.reservationService.getReservationInfo(this.reservationId).subscribe(res => { + this.processReservationInfo(res); + if (!this.reservationFinalized) { + pollReservationStatus(this.reservationId, this.reservationService, res1 => this.processReservationInfo(res1)); + } + }); + } + + private processReservationInfo(res: ReservationInfo) { + if (embedded && this.event.embeddingConfiguration.enabled) { + window.parent.postMessage( + new ReservationStatusChanged(res.status, this.reservationId), + this.event.embeddingConfiguration.notificationOrigin + ); + } + this.reservationInfo = res; + // + this.reservationFinalized = res.status !== 'FINALIZING'; + this.ticketsAllAssigned = res.status !== 'FINALIZING'; + this.invoiceReceiptReady = res.metadata.readyForConfirmation; + this.unlockedTicketCount = 0; + // + res.ticketsByCategory.forEach((tc) => { + tc.tickets.forEach((ticket: Ticket) => { + this.buildFormControl(ticket); + if (!ticket.locked) { + this.unlockedTicketCount += 1; + } + this.ticketsAllAssigned = this.ticketsAllAssigned && ticket.assigned; + }); + }); + } + + private buildFormControl(ticket: Ticket): void { + this.ticketsFormControl[ticket.uuid] = this.ticketService.buildFormGroupForTicket(ticket); + } + + sendEmailForTicket(ticketIdentifier: string): void { + this.ticketService.sendTicketByEmail(this.eventShortName, ticketIdentifier).subscribe(res => { + if (res) { + this.sendEmailForTicketStatus[ticketIdentifier] = true; + } + }); + } + + reSendReservationEmail(): void { + this.reservationService.reSendReservationEmail('event', this.eventShortName, this.reservationId, this.i18nService.getCurrentLang()).subscribe(res => { + this.reservationMailSent = res; + }); + } + + updateTicket(uuid: string): void { + const ticketValue = this.ticketsFormControl[uuid].value; + this.ticketService.updateTicket(this.event.shortName, uuid, ticketValue).subscribe(res => { + if (res.success) { + this.loadReservation(); + this.hideTicketForm(uuid); + } + }, (err) => { + handleServerSideValidationError(err, this.ticketsFormControl[uuid]); + }); + } + + releaseTicket(ticket: Ticket) { + this.ticketService.openReleaseTicket(ticket, this.event.shortName) + .subscribe(released => { + if (released) { + const singleTicket = this.reservationInfo.ticketsByCategory.map((c) => c.tickets.length).reduce((c1, c2) => c1 + c2) === 1; + if (singleTicket) { + this.router.navigate(['event', this.event.shortName], {replaceUrl: true}); + } else { + this.loadReservation(); + } + } + }); + } + + get ticketFormVisible(): boolean { + return Object.keys(this.ticketsFormShow).length > 0; + } + + hideTicketForm(uuid: string): void { + delete this.ticketsFormShow[uuid]; + } + + get downloadBillingDocumentVisible(): boolean { + return this.invoiceReceiptReady + && this.event.invoicingConfiguration.userCanDownloadReceiptOrInvoice + && this.reservationInfo.paid + && this.reservationInfo.invoiceOrReceiptDocumentPresent; + } + + public isOnlineTicket(category: TicketsByTicketCategory): boolean { + return this.event.format === 'ONLINE' + || (this.event.format === 'HYBRID' && category.ticketAccessType === 'ONLINE'); + } + + get purchaseContextTitle(): string { + return this.event.title[this.translateService.currentLang]; + } + + get showReservationButtons(): boolean { + return this.reservationFinalized + && (!embedded || !this.event.embeddingConfiguration.enabled) + && !this.reservationInfo.metadata.hideConfirmationButtons; + } + + get walletIntegrationEnabled(): boolean { + return this.walletConfiguration != null && + (this.walletConfiguration.gWalletEnabled || this.walletConfiguration.passEnabled); + } + + downloadTicket(ticket: Ticket): void { + this.ticketService.openDownloadTicket(ticket, this.eventShortName, this.walletConfiguration) + .subscribe(() => {}); + } +} diff --git a/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.html b/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.html new file mode 100644 index 0000000000..7d22345722 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.html @@ -0,0 +1,53 @@ +<table class="table"> + <thead> + <tr> + <th translate="reservation-page.category"></th> + <th class="text-center" translate="reservation-page.amount"></th> + <th class="text-end" translate="reservation-page.price"></th> + <th class="text-end" translate="reservation-page.subtotal"></th> + </tr> + </thead> + <tbody> + <tr *ngFor="let summaryRow of reservationInfo.orderSummary.summary" [class.bg-light]="summaryRow.type === 'TAX_DETAIL'"> + <td> + <span *ngIf="summaryRow.type === 'DYNAMIC_DISCOUNT'" translate="reservation.dynamic.discount.description"></span> + <span *ngIf="summaryRow.type === 'APPLIED_SUBSCRIPTION'"> + {{'summary.table.subscription'|translate: {'0': isSubscriptionPurchaseContext ? purchaseContext.title[currentLang] : summaryRow.name } }} + <span *ngIf="displayRemoveSubscription"> + <button type="button" class="btn btn-link delete-button" (click)="removeSubscription.emit(summaryRow);" [ngbTooltip]="'summary.table.remove-applied-subscription-from-order'|translate"> + <span class="sr-only" translate="summary.table.remove-applied-subscription-from-order"></span> + <fa-icon [icon]="['fas', 'trash']" a11yRole="presentation"></fa-icon> + </button> + </span> + </span> + <span *ngIf="summaryRow.type === 'TAX_DETAIL'"> + {{'reservation-page.vat'|translate:{'0': summaryRow.taxPercentage, '1': ('common.vat' | translate)} }} + </span> + <span *ngIf="summaryRow.type !== 'DYNAMIC_DISCOUNT' && summaryRow.type !== 'APPLIED_SUBSCRIPTION' && summaryRow.type !== 'TAX_DETAIL' ">{{summaryRow.name}}</span> + </td> + <td class="text-center"><ng-container *ngIf="summaryRow.type !== 'TAX_DETAIL'">{{summaryRow.amount}}</ng-container></td> + <td class="text-end">{{summaryRow.price}}</td> + <td class="text-end"><app-price-tag [purchaseContext]="purchaseContext" [formattedPrice]="summaryRow.subTotal"></app-price-tag></td> + </tr> + </tbody> + <tfoot> + <tr *ngIf="!reservationInfo.orderSummary.free && reservationInfo.orderSummary.displayVat && !purchaseContext.vatIncluded" class="bg-light"> + <td colspan="3">{{'reservation-page.vat'|translate:{'0': purchaseContext.vat, '1': ('common.vat' | translate)} }}</td> + <td class="text-end"><app-price-tag [purchaseContext]="purchaseContext" [formattedPrice]="reservationInfo.orderSummary.totalVAT"></app-price-tag></td> + </tr> + <tr> + <th colspan="3" translate="reservation-page.total"><span *ngIf="displaySplitPaymentNote"> *</span></th> + <td class="text-end"><strong><app-price-tag [purchaseContext]="purchaseContext" [formattedPrice]="reservationInfo.orderSummary.totalPrice"></app-price-tag></strong></td> + </tr> + <tr *ngIf="!reservationInfo.orderSummary.free && reservationInfo.orderSummary.displayVat && purchaseContext.vatIncluded"> + <td colspan="3">{{'reservation-page.vat-included'|translate: {'0': purchaseContext.vat, '1': ('common.vat' | translate)} }}</td> + <td class="text-end"><app-price-tag [purchaseContext]="purchaseContext" [formattedPrice]="reservationInfo.orderSummary.totalVAT"></app-price-tag></td> + </tr> + <tr *ngIf="!reservationInfo.orderSummary.free && !reservationInfo.orderSummary.displayVat"> + <td colspan="4">{{'invoice.vat-voided'|translate: {'0': ('common.vat' | translate)} }}</td> + </tr> + <tr *ngIf="displaySplitPaymentNote"> + <td colspan="4">* {{'invoice.vat-not-added'|translate: {'0': ('common.vat' | translate)} }}</td> + </tr> + </tfoot> +</table> diff --git a/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.scss b/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.scss new file mode 100644 index 0000000000..852ce54f4a --- /dev/null +++ b/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.scss @@ -0,0 +1,7 @@ +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; + +.delete-button { + color: $gray-600; +} diff --git a/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.ts b/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.ts new file mode 100644 index 0000000000..08b6f35e25 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/summary-table/summary-table.component.ts @@ -0,0 +1,45 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {ReservationInfo, SummaryRow} from '../../model/reservation-info'; +import {PurchaseContext} from '../../model/purchase-context'; +import {PurchaseContextType} from '../../shared/purchase-context.service'; +import {TranslateService} from '@ngx-translate/core'; + +@Component({ + selector: 'app-summary-table', + templateUrl: './summary-table.component.html', + styleUrls: ['./summary-table.component.scss'] +}) +export class SummaryTableComponent { + + @Input() + reservationInfo: ReservationInfo; + + @Input() + purchaseContext: PurchaseContext; + + @Input() + displayRemoveSubscription: boolean; + + @Input() + purchaseContextType: PurchaseContextType; + + @Output() + removeSubscription: EventEmitter<SummaryRow> = new EventEmitter<SummaryRow>(); + + constructor(private translateService: TranslateService) { + } + + get currentLang(): string { + return this.translateService.currentLang; + } + + get isSubscriptionPurchaseContext(): boolean { + return this.purchaseContextType === 'subscription'; + } + + get displaySplitPaymentNote(): boolean { + return !this.reservationInfo.orderSummary.free + && this.reservationInfo.billingDetails.invoicingAdditionalInfo?.italianEInvoicing?.splitPayment; + } + +} diff --git a/frontend/projects/public/src/app/reservation/ticket-form/ticket-form.component.html b/frontend/projects/public/src/app/reservation/ticket-form/ticket-form.component.html new file mode 100644 index 0000000000..fb7642468f --- /dev/null +++ b/frontend/projects/public/src/app/reservation/ticket-form/ticket-form.component.html @@ -0,0 +1,49 @@ +<div class="row"> + <div *ngIf="ticket" [formGroup]="form" class="col"> + <div *ngFor="let field of ticket.ticketFieldConfigurationBeforeStandard" class="row g-2 mb-3"> + <div class="col-12"> + <app-additional-field [field]="field" [ticketUUID]="ticket.uuid" [form]="getAdditional(form)" [ticketAcquired]="ticket.acquired"></app-additional-field> + </div> + </div> + + <div class="row g-2 mb-3"> + <div class="col-12 col-md-6"> + <div class="form-group"> + <label class="form-label" [attr.for]="ticket.uuid + '-first-name'">{{'common.first-name' | translate}} *</label> + <input [attr.id]="ticket.uuid + '-first-name'" formControlName="firstName" type="text" [ngClass]="{'form-control': ticket.locked !== true, 'form-control-plaintext': ticket.locked}" appInvalidFeedback [readonly]="ticket.locked === true"> + </div> + </div> + <div class="col-12 col-md-6"> + <div class="form-group"> + <label class="form-label" [attr.for]="ticket.uuid + '-last-name'">{{'common.last-name' | translate}} *</label> + <input [attr.id]="ticket.uuid + '-last-name'" formControlName="lastName" type="text" [ngClass]="{'form-control': ticket.locked !== true, 'form-control-plaintext': ticket.locked}" appInvalidFeedback [readonly]="ticket.locked === true"> + </div> + </div> + </div> + <div class="row g-2 mb-3"> + <div class="col-12"> + <div class="form-group"> + <label class="form-label" [attr.for]="ticket.uuid + '-email'">{{'common.email' | translate}} *</label> + <input [attr.id]="ticket.uuid + '-email'" formControlName="email" type="email" [ngClass]="{'form-control': !emailEditForbidden, 'form-control-plaintext': emailEditForbidden}" appInvalidFeedback [readonly]="emailEditForbidden"> + </div> + </div> + </div> + <div class="row g-2 mb-3" *ngFor="let field of ticket.ticketFieldConfigurationAfterStandard"> + <div class="col-12"> + <app-additional-field [field]="field" [ticketUUID]="ticket.uuid" [form]="getAdditional(form)" [ticketAcquired]="ticket.acquired"></app-additional-field> + </div> + </div> + <div class="row g-2 mb-3"> + <div class="col-12"> + <div class="form-group" *ngIf="purchaseContext && purchaseContext.contentLanguages.length > 1"> + <label class="form-label" [attr.for]="ticket.uuid + '-userLanguage'" translate="reservation-page-complete.language"></label> + <select [attr.id]="ticket.uuid + '-userLanguage'" formControlName="userLanguage" class="form-select" appInvalidFeedback> + <option value=""></option> + <option *ngFor="let lang of purchaseContext.contentLanguages" [ngValue]="lang.locale">{{lang.displayLanguage}}</option> + </select> + </div> + </div> + </div> + </div> + +</div> diff --git a/frontend/projects/public/src/app/reservation/ticket-form/ticket-form.component.ts b/frontend/projects/public/src/app/reservation/ticket-form/ticket-form.component.ts new file mode 100644 index 0000000000..9088ac79fa --- /dev/null +++ b/frontend/projects/public/src/app/reservation/ticket-form/ticket-form.component.ts @@ -0,0 +1,40 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {UntypedFormGroup} from '@angular/forms'; +import {Ticket} from '../../model/ticket'; +import {PurchaseContext} from '../../model/purchase-context'; +import {ReservationMetadata} from '../../model/reservation-info'; + +@Component({ + selector: 'app-ticket-form', + templateUrl: './ticket-form.component.html' +}) +export class TicketFormComponent implements OnInit { + + @Input() + form: UntypedFormGroup; + + @Input() + ticket: Ticket; + + @Input() + purchaseContext: PurchaseContext; + + @Input() + reservationMetadata: ReservationMetadata; + + constructor() { } + + public ngOnInit(): void { + if (this.form && this.purchaseContext && this.purchaseContext.contentLanguages && this.purchaseContext.contentLanguages.length === 1) { + this.form.get('userLanguage').setValue(this.purchaseContext.contentLanguages[0].locale); + } + } + + getAdditional(form: UntypedFormGroup) { + return form.get('additional') as UntypedFormGroup; + } + + get emailEditForbidden(): boolean { + return this.ticket.locked || (this.reservationMetadata?.lockEmailEdit && this.ticket.email != null); + } +} diff --git a/frontend/projects/public/src/app/shared/analytics.service.ts b/frontend/projects/public/src/app/shared/analytics.service.ts new file mode 100644 index 0000000000..9ea402e047 --- /dev/null +++ b/frontend/projects/public/src/app/shared/analytics.service.ts @@ -0,0 +1,55 @@ +import {Injectable} from '@angular/core'; +import {AnalyticsConfiguration} from '../model/analytics-configuration'; +import {Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AnalyticsService { + + private gaScript: Observable<[Function, AnalyticsConfiguration, string]> = null; + + constructor() { + } + + pageView(conf: AnalyticsConfiguration): void { + const locationPathName = location.pathname; + if (conf.googleAnalyticsKey) { + this.handleGoogleAnalytics(conf, locationPathName); + } + } + + + private handleGoogleAnalytics(conf: AnalyticsConfiguration, locationPathName: string) { + if (this.gaScript === null) { + this.gaScript = new Observable<[Function, AnalyticsConfiguration, string]>(subscribe => { + if (!document.getElementById('GA_SCRIPT')) { // <- script is not created + const scriptElem = document.createElement('script'); + scriptElem.id = 'GA_SCRIPT'; + scriptElem.addEventListener('load', () => { + subscribe.next([window['ga'], conf, locationPathName]); + }); + scriptElem.src = 'https://ssl.google-analytics.com/analytics.js'; + scriptElem.async = true; + scriptElem.defer = true; + document.body.appendChild(scriptElem); + } else if (!window['ga']) { // <- script has been created, but not loaded + document.getElementById('GA_SCRIPT').addEventListener('load', () => { + subscribe.next([window['ga'], conf, locationPathName]); + }); + } else { // <- script has been loaded + subscribe.next([window['ga'], conf, locationPathName]); + } + }); + } + + this.gaScript.subscribe(([ga, configuration, pathname]) => { + if (configuration.googleAnalyticsScrambledInfo) { + ga('create', configuration.googleAnalyticsKey, {'anonymizeIp': true, 'storage': 'none', 'clientId': configuration.clientId}); + } else { + ga('create', configuration.googleAnalyticsKey); + } + ga('send', 'pageview', pathname); + }); + } +} diff --git a/frontend/projects/public/src/app/shared/clipboard-copy/clipboard-copy.directive.ts b/frontend/projects/public/src/app/shared/clipboard-copy/clipboard-copy.directive.ts new file mode 100644 index 0000000000..ac0ebae1ba --- /dev/null +++ b/frontend/projects/public/src/app/shared/clipboard-copy/clipboard-copy.directive.ts @@ -0,0 +1,35 @@ +import {Directive, EventEmitter, HostListener, Input, Output} from '@angular/core'; + +/** + * Based upon https://stackoverflow.com/a/52949299/9679084 + */ +@Directive({ selector: '[appClipboardCopy]' }) +export class ClipboardCopyDirective { + + // tslint:disable-next-line:no-input-rename + @Input('appClipboardCopy') + public payload: string; + + @Output() + public copied: EventEmitter<string> = new EventEmitter<string>(); + + @HostListener('click', ['$event']) + public onClick(event: MouseEvent): void { + + event.preventDefault(); + if (!this.payload) { + return; + } + + const listener = (e: ClipboardEvent) => { + const clipboard = e.clipboardData || window['clipboardData']; + clipboard.setData('text', this.payload.toString()); + e.preventDefault(); + this.copied.emit(this.payload); + }; + + document.addEventListener('copy', listener, false); + document.execCommand('copy'); + document.removeEventListener('copy', listener, false); + } +} diff --git a/frontend/projects/public/src/app/shared/custom-css-helper.ts b/frontend/projects/public/src/app/shared/custom-css-helper.ts new file mode 100644 index 0000000000..e739119088 --- /dev/null +++ b/frontend/projects/public/src/app/shared/custom-css-helper.ts @@ -0,0 +1,31 @@ +import {Event} from '../model/event'; + +export function removeAllCustomEventCss() { + document.head.querySelectorAll('style[data-event-custom-css]').forEach(e => e.remove()); +} + +export function handleCustomCss(event: Event): void { + if (event.customCss === null) { + //remove all custom event styles, as this event does not have any override + removeAllCustomEventCss(); + } else { + const id = 'event-custom-css-' + event.organizationName + '-' + event.shortName; + const a = document.getElementById('event-custom-css-' + event.organizationName + '-' + event.shortName); + const found = document.head.querySelectorAll('style[data-event-custom-css]'); + + //remove all custom event styles already present that don't have the expected id + found.forEach(e => { + if (e.id !== id) {e.remove()} + }); + + //create custom css + if (a === null) { + const style = document.createElement('style'); + style.setAttribute('id', id); + style.setAttribute('type', 'text/css') + style.setAttribute('data-event-custom-css', 'true'); + style.textContent = event.customCss; + document.head.appendChild(style) + } + } +} diff --git a/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.html b/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.html new file mode 100644 index 0000000000..5dca94b78e --- /dev/null +++ b/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.html @@ -0,0 +1,9 @@ +<app-topbar [contentLanguages]="purchaseContext.contentLanguages" [displayLoginButton]="displayTopLoginButton"></app-topbar> +<div class="row"> + <div class="col-12" [ngClass]="{'col-sm-7 col-md-4': isEvent}"> + <img class="img-responsive" alt="" aria-hidden="true" [src]="'/file/'+purchaseContext.fileBlobId" role="presentation"> + </div> + <div class="col-12 col-sm-5 col-md-8 text-center" *ngIf="isEvent"> + <h1>{{ title }}</h1> + </div> +</div> diff --git a/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.scss b/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.scss new file mode 100644 index 0000000000..58a9013ac8 --- /dev/null +++ b/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.scss @@ -0,0 +1,11 @@ +.img-responsive { + max-height: 100px; + margin: auto; + display: block; + max-width: 100%; + height: auto; +} + +h1 { + margin-top:20px; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.ts b/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.ts new file mode 100644 index 0000000000..aed2dfc4ae --- /dev/null +++ b/frontend/projects/public/src/app/shared/event-header/purchase-context-header.component.ts @@ -0,0 +1,81 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {I18nService} from '../i18n.service'; +import {removeDOMNode} from '../event.service'; +import {PurchaseContext} from '../../model/purchase-context'; +import {Event} from '../../model/event'; +import {PurchaseContextType} from '../purchase-context.service'; +import {TranslateService} from '@ngx-translate/core'; + +@Component({ + selector: 'app-purchase-context-header', + templateUrl: './purchase-context-header.component.html', + styleUrls: ['./purchase-context-header.component.scss'] +}) +export class PurchaseContextHeaderComponent implements OnInit, OnDestroy { + + @Input() + purchaseContext: PurchaseContext; + + @Input() + type: PurchaseContextType; + + @Input() + displayTopLoginButton = true; + + schemaElem: HTMLScriptElement; + + constructor(private i18nService: I18nService, private translateService: TranslateService) { + } + + ngOnInit() { + // https://developers.google.com/search/docs/data-types/event + // https://search.google.com/test/rich-results?utm_campaign=devsite&utm_medium=jsonld&utm_source=event&id=tqxuXf4XIb4xolzr7EiE2Q + + if (this.type === 'event') { + + const ev = this.purchaseContext as Event; + const start = new Date(ev.datesWithOffset.startDateTime); + const end = new Date(ev.datesWithOffset.endDateTime); + const descriptionHolder = document.createElement('div'); + descriptionHolder.innerHTML = ev.description[this.i18nService.getCurrentLang()]; + + const jsonSchema = { + '@context': 'https://schema.org', + '@type': 'Event', + 'name' : ev.title[this.i18nService.getCurrentLang()], + 'startDate': start.toISOString(), + 'endDate': end.toISOString(), + 'description': descriptionHolder.innerText.trim(), + 'location': { + '@type': 'Place', + 'address': ev.location + }, + 'organizer': { + '@type': 'Organization', + 'name': ev.organizationName, + 'email': ev.organizationEmail + }, + 'image': '/file/' + ev.fileBlobId + }; + + this.schemaElem = document.createElement('script'); + this.schemaElem.text = JSON.stringify(jsonSchema); + this.schemaElem.type = 'application/ld+json'; + document.head.appendChild(this.schemaElem); + } + } + + get title(): string { + return this.purchaseContext.title[this.translateService.currentLang]; + } + + get isEvent(): boolean { + return this.type === 'event'; + } + + ngOnDestroy() { + if (this.type === 'event') { + removeDOMNode(this.schemaElem); + } + } +} diff --git a/frontend/projects/public/src/app/shared/event.service.ts b/frontend/projects/public/src/app/shared/event.service.ts new file mode 100644 index 0000000000..19c7b53057 --- /dev/null +++ b/frontend/projects/public/src/app/shared/event.service.ts @@ -0,0 +1,92 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable, of} from 'rxjs'; +import {BasicEventInfo} from '../model/basic-event-info'; +import {Event} from '../model/event'; +import {ItemsByCategory} from '../model/items-by-category'; +import {WaitingListSubscriptionRequest} from '../model/waiting-list-subscription-request'; +import {ValidatedResponse} from '../model/validated-response'; +import {shareReplay} from 'rxjs/operators'; +import {EventCode} from '../model/event-code'; +import {DateValidity} from '../model/date-validity'; +import {SearchParams} from '../model/search-params'; + +@Injectable({ + providedIn: 'root' +}) +export class EventService { + + private eventCache: {[key: string]: Observable<Event>} = {}; + + constructor(private http: HttpClient) { } + + getEvents(searchParams?: SearchParams): Observable<BasicEventInfo[]> { + const params = searchParams?.toHttpParams(); + return this.http.get<BasicEventInfo[]>('/api/v2/public/events', { + responseType: 'json', + params + }); + } + + getEvent(eventShortName: string): Observable<Event> { + // caching as explained with https://blog.angularindepth.com/fastest-way-to-cache-for-lazy-developers-angular-with-rxjs-444a198ed6a6 + if (!this.eventCache[eventShortName]) { + const preloadEvent = document.getElementById('preload-event'); + if (preloadEvent && preloadEvent.getAttribute('data-param') === eventShortName) { + this.eventCache[eventShortName] = of(JSON.parse(preloadEvent.textContent)).pipe(shareReplay(1)); + } else { + this.eventCache[eventShortName] = this.http.get<Event>(`/api/v2/public/event/${eventShortName}`).pipe(shareReplay(1)); + } + setTimeout(() => { + delete this.eventCache[eventShortName]; + }, 60000 * 20); // clean up cache after 20 minutes + } + + return this.eventCache[eventShortName]; + } + + getEventTicketsInfo(eventShortName: string, code?: string): Observable<ItemsByCategory> { + const params = code ? {params: {code: code}} : {}; + return this.http.get<ItemsByCategory>(`/api/v2/public/event/${eventShortName}/ticket-categories`, params); + } + + submitWaitingListSubscriptionRequest(eventShortName: string, waitingListSubscriptionRequest: WaitingListSubscriptionRequest): Observable<ValidatedResponse<boolean>> { + return this.http.post<ValidatedResponse<boolean>>(`/api/v2/public/event/${eventShortName}/waiting-list/subscribe`, waitingListSubscriptionRequest); + } + + validateCode(eventShortName: string, code: string): Observable<ValidatedResponse<EventCode>> { + return this.http.get<ValidatedResponse<EventCode>>(`/api/v2/public/event/${eventShortName}/validate-code`, {params: {code: code}}); + } +} + +export function shouldDisplayTimeZoneInfo(provider: DateValidity): boolean { + const datesWithOffset = provider.datesWithOffset; + return isDifferentTimeZone(datesWithOffset.startDateTime, datesWithOffset.startTimeZoneOffset) + || isDifferentTimeZone(datesWithOffset.endDateTime, datesWithOffset.endTimeZoneOffset); +} + +export function isDifferentTimeZone(serverTs: number, serverOffset: number): boolean { + // client: + // The time-zone offset is the difference, in minutes, from local time to UTC. + // Note that this means that the offset is positive if the local timezone is behind UTC and negative if it is ahead. + // source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset + // + // server: + // nothing special, the offset is returned as it should be (positive if ahead of UTC, negative otherwise), in seconds + + const clientOffset = new Date(serverTs).getTimezoneOffset() * 60; + return (clientOffset + serverOffset) !== 0; +} + +/** + * This is a polyfill for Node.remove(), which is not supported in IE + * @param node the Node to remove + * @returns true if the Node has been removed, false otherwise + */ +export function removeDOMNode(node: Node): boolean { + if (node != null && node.parentNode != null) { + node.parentNode.removeChild(node); + return true; + } + return false; +} diff --git a/frontend/projects/public/src/app/shared/feedback/feedback.component.html b/frontend/projects/public/src/app/shared/feedback/feedback.component.html new file mode 100644 index 0000000000..9b62bb3505 --- /dev/null +++ b/frontend/projects/public/src/app/shared/feedback/feedback.component.html @@ -0,0 +1,10 @@ +<ngb-toast (hide)="hide()" [autohide]="true" [delay]="2500" *ngIf="active" [ngClass]="boxClass"> + <div class="d-flex"> + <div class="ps-2 pe-2 d-flex justify-content-center align-items-center" [ngClass]="headerClass"> + <fa-icon [icon]="boxIcon" size="2x"></fa-icon> + </div> + <div class="p-2 d-flex align-items-center"> + <span class="message">{{text | translate}}</span> + </div> + </div> +</ngb-toast> diff --git a/frontend/projects/public/src/app/shared/feedback/feedback.component.scss b/frontend/projects/public/src/app/shared/feedback/feedback.component.scss new file mode 100644 index 0000000000..61167096a4 --- /dev/null +++ b/frontend/projects/public/src/app/shared/feedback/feedback.component.scss @@ -0,0 +1,34 @@ +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/mixins"; +@import "bootstrap/scss/variables"; + +:host ::ng-deep .toast { + + @include media-breakpoint-down(lg) { + position: fixed; + top: 0; + left: 0; + margin: 2em; + max-width: unset; + background-color: white; + } + + @include media-breakpoint-up(lg) { + position: fixed; + bottom: 20px; + right: 0; + margin: 2rem; + } + + padding: 0; + z-index: 101; + + .toast-body { + padding: 0; + } +} + +.message { + padding: 1rem; + font-size: $font-size-base * 1.1; +} diff --git a/frontend/projects/public/src/app/shared/feedback/feedback.component.ts b/frontend/projects/public/src/app/shared/feedback/feedback.component.ts new file mode 100644 index 0000000000..d143fbdaf3 --- /dev/null +++ b/frontend/projects/public/src/app/shared/feedback/feedback.component.ts @@ -0,0 +1,62 @@ +import {Component} from '@angular/core'; +import {FeedbackService} from './feedback.service'; +import {FeedbackType} from '../../model/feedback'; +import {IconProp} from '@fortawesome/fontawesome-svg-core'; + + +@Component({ + selector: 'app-feedback', + templateUrl: './feedback.component.html', + styleUrls: ['./feedback.component.scss'] +}) +export class FeedbackComponent { + text: string; + active: boolean; + private type: FeedbackType; + + constructor(private feedbackService: FeedbackService) { + this.feedbackService.displayNotification().subscribe(details => { + this.active = details.active; + this.text = details.message; + this.type = details.type || 'INFO'; + }); + } + + public hide(): void { + this.active = false; + } + + get boxClass(): string { + switch (this.type) { + case 'SUCCESS': + return 'border-success text-success'; + case 'ERROR': + return 'border-danger text-danger'; + case 'INFO': + return 'border-primary text-primary'; + } + } + + get headerClass(): string { + switch (this.type) { + case 'SUCCESS': + return 'bg-success text-white'; + case 'ERROR': + return 'bg-danger text-white'; + case 'INFO': + return 'bg-white text-primary'; + } + } + + get boxIcon(): IconProp { + switch (this.type) { + case 'SUCCESS': + return ['far', 'check-circle']; + case 'ERROR': + return ['fas', 'exclamation-circle']; + case 'INFO': + return ['fas', 'info-circle']; + } + } + +} diff --git a/frontend/projects/public/src/app/shared/feedback/feedback.service.ts b/frontend/projects/public/src/app/shared/feedback/feedback.service.ts new file mode 100644 index 0000000000..2450c0fa41 --- /dev/null +++ b/frontend/projects/public/src/app/shared/feedback/feedback.service.ts @@ -0,0 +1,44 @@ +import {Injectable} from '@angular/core'; +import {Observable, Subject} from 'rxjs'; +import {FeedbackContent} from '../../model/feedback'; + +@Injectable({ + providedIn: 'root' +}) +export class FeedbackService { + + private toastSubject = new Subject<FeedbackContent>(); + + public showSuccess(message: string) { + this.toastSubject.next({ + active: true, + message, + type: 'SUCCESS' + }); + } + + public showError(message: string) { + this.toastSubject.next({ + active: true, + message, + type: 'ERROR' + }); + } + + public showInfo(message: string) { + this.toastSubject.next({ + active: true, + message, + type: 'INFO' + }); + } + + public hide() { + this.toastSubject.next({ active: false }); + } + + public displayNotification(): Observable<FeedbackContent> { + return this.toastSubject; + } + +} diff --git a/frontend/projects/public/src/app/shared/i18n.service.ts b/frontend/projects/public/src/app/shared/i18n.service.ts new file mode 100644 index 0000000000..37405565c3 --- /dev/null +++ b/frontend/projects/public/src/app/shared/i18n.service.ts @@ -0,0 +1,131 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of, zip } from 'rxjs'; +import { Language } from '../model/event'; +import { LocalizedCountry } from '../model/localized-country'; +import { Title } from '@angular/platform-browser'; +import {TranslateService, TranslateLoader, LangChangeEvent} from '@ngx-translate/core'; +import { Router, NavigationStart } from '@angular/router'; +import { map, mergeMap, shareReplay, catchError } from 'rxjs/operators'; +import { EventService } from './event.service'; +import { PurchaseContextType } from './purchase-context.service'; +import {PurchaseContext} from '../model/purchase-context'; +import {getFromSessionStorage, writeToSessionStorage} from './util'; + +@Injectable({ + providedIn: 'root' +}) +export class I18nService { + + + private applicationLanguages: Observable<Language[]>; + + private static getInterpolateParams(lang: string, ctx: PurchaseContext): any { + if (ctx != null) { + return {'0': ctx.title[lang]}; + } + return null; + } + + constructor( + private http: HttpClient, + private title: Title, + private translateService: TranslateService, + private router: Router, + private customLoader: CustomLoader, + private eventService: EventService) { } + + getCountries(locale: string): Observable<LocalizedCountry[]> { + return this.http.get<LocalizedCountry[]>(`/api/v2/public/i18n/countries/${locale}`); + } + + getVatCountries(locale: string): Observable<LocalizedCountry[]> { + return this.http.get<LocalizedCountry[]>(`/api/v2/public/i18n/countries-vat/${locale}`); + } + + getEUVatCountries(locale: string): Observable<LocalizedCountry[]> { + return this.http.get<LocalizedCountry[]>(`/api/v2/public/i18n/eu-countries-vat/${locale}`); + } + + getAvailableLanguages(): Observable<Language[]> { + if (!this.applicationLanguages) { + this.applicationLanguages = this.http.get<Language[]>(`/api/v2/public/i18n/languages`).pipe(shareReplay(1)); + } + return this.applicationLanguages; + } + + setPageTitle(titleCode: string, ctx?: PurchaseContext): void { + + const titleSub = this.translateService.onLangChange.subscribe((params: LangChangeEvent) => { + this.title.setTitle(this.translateService.instant(titleCode, I18nService.getInterpolateParams(params.lang, ctx))); + }); + + const routerSub = this.router.events.subscribe(ev => { + if (ev instanceof NavigationStart) { + routerSub.unsubscribe(); + titleSub.unsubscribe(); + this.title.setTitle(null); + } + }); + + this.title.setTitle(this.translateService.instant(titleCode, I18nService.getInterpolateParams(this.translateService.currentLang, ctx))); + } + + persistLanguage(lang: string): void { + writeToSessionStorage('ALFIO_LANG', lang); + } + + getPersistedLanguage(): string { + return getFromSessionStorage('ALFIO_LANG'); + } + + getCurrentLang(): string { + return this.translateService.currentLang; + } + + useTranslation(type: PurchaseContextType, publicIdentifier: string, lang: string): Observable<boolean> { + const overrideBundle = this.getOverrideBundle(type, publicIdentifier, lang); + return zip(this.customLoader.getTranslation(lang), overrideBundle).pipe(mergeMap(([root, override]) => { + this.translateService.setTranslation(lang, root, false); + this.translateService.setTranslation(lang, override, true); + this.translateService.use(lang); + return of(true); + })); + } + + useTranslationForRoot(lang: string): Observable<boolean> { + return this.useTranslation(null, '', lang); + } + + private getOverrideBundle(type: PurchaseContextType, publicIdentifier: string, lang: string): Observable<any> { + if (type === 'event' && publicIdentifier) { + return this.eventService.getEvent(publicIdentifier) + .pipe( + catchError(e => of({i18nOverride: {}})), + map(e => e.i18nOverride[lang] || {}) + ); + } + return of({}); + } +} + +const translationCache: {[key: string]: Observable<any>} = {}; + +@Injectable({providedIn: 'root'}) +export class CustomLoader implements TranslateLoader { + + constructor(private http: HttpClient) { + } + + getTranslation(lang: string): Observable<any> { + if (!translationCache[lang]) { + const preloadBundle = document.getElementById('preload-bundle'); + if (preloadBundle && preloadBundle.getAttribute('data-param') === lang) { + translationCache[lang] = of(JSON.parse(preloadBundle.textContent)).pipe(shareReplay(1)); + } else { + translationCache[lang] = this.http.get(`/api/v2/public/i18n/bundle/${lang}`).pipe(shareReplay(1)); + } + } + return translationCache[lang]; + } +} diff --git a/frontend/projects/public/src/app/shared/info.service.ts b/frontend/projects/public/src/app/shared/info.service.ts new file mode 100644 index 0000000000..526ca847a1 --- /dev/null +++ b/frontend/projects/public/src/app/shared/info.service.ts @@ -0,0 +1,28 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable, of} from 'rxjs'; +import {Info} from '../model/info'; +import {shareReplay} from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class InfoService { + + private infoCache: Observable<Info>; + + constructor(private http: HttpClient) { } + + getInfo(): Observable<Info> { + if (!this.infoCache) { + const preloadInfo = document.getElementById('preload-info'); + if (preloadInfo) { + this.infoCache = of(JSON.parse(preloadInfo.textContent)).pipe(shareReplay(1)); + } else { + this.infoCache = this.http.get<Info>('/api/v2/info').pipe(shareReplay(1)); + } + setTimeout(() => { this.infoCache = null; }, 60000 * 10); // clean up cache after 10 minute + } + return this.infoCache; + } +} diff --git a/frontend/projects/public/src/app/shared/invalid-feedback.directive.ts b/frontend/projects/public/src/app/shared/invalid-feedback.directive.ts new file mode 100644 index 0000000000..b24fbbf183 --- /dev/null +++ b/frontend/projects/public/src/app/shared/invalid-feedback.directive.ts @@ -0,0 +1,122 @@ +import {Directive, ElementRef, Input, OnDestroy, OnInit, Optional} from '@angular/core'; +import {AbstractControl, FormControlName, UntypedFormGroup, ValidationErrors} from '@angular/forms'; +import {TranslateService} from '@ngx-translate/core'; +import {Subscription} from 'rxjs'; +import {ErrorDescriptor} from '../model/validated-response'; +import {removeDOMNode} from './event.service'; + +@Directive({ + selector: '[appInvalidFeedback]' +}) +export class InvalidFeedbackDirective implements OnInit, OnDestroy { + + private subs: Subscription[] = []; + + @Input() + invalidFeedbackInLabel: boolean; + + @Input() + invalidFeedbackFieldName: string; + + @Input() + invalidFeedbackForm: UntypedFormGroup; + + private targetControl: AbstractControl; + + constructor(private element: ElementRef, @Optional() private control: FormControlName, private translation: TranslateService) { + } + + ngOnInit(): void { + + if (this.control == null && this.invalidFeedbackForm != null && this.invalidFeedbackFieldName != null) { + this.targetControl = this.invalidFeedbackForm.get(this.invalidFeedbackFieldName); + this.targetControl.statusChanges.subscribe(e => { + this.checkValidation(); + }); + } + + if (this.control) { + this.control.statusChanges.subscribe(e => { + this.checkValidation(); + }); + } + } + + private clearSubs(): void { + this.subs.forEach(s => { + if (s) { + s.unsubscribe(); + } + }); + this.subs = []; + } + + ngOnDestroy(): void { + this.clearSubs(); + } + + private get errors(): ValidationErrors { + return this.targetControl ? this.targetControl.errors : this.control.errors; + } + + private checkValidation(): void { + + let errorContainerElement: HTMLElement; + + if (this.invalidFeedbackInLabel) { + errorContainerElement = this.element.nativeElement.parentElement.nextElementSibling; + } else { + errorContainerElement = this.element.nativeElement.nextElementSibling; + } + + this.clearSubs(); + if (this.errors && this.errors.serverError && this.errors.serverError.length > 0) { + this.element.nativeElement.classList.add('is-invalid'); + if (isInvalidFeedbackContainer(errorContainerElement)) { + // remove messages that are already presents + const rangeObj = new Range(); + rangeObj.selectNodeContents(errorContainerElement); + rangeObj.deleteContents(); + this.addErrorMessages(errorContainerElement); + // + } else { + const container = document.createElement('div'); + container.classList.add('invalid-feedback'); + this.addErrorMessages(container); + if (this.invalidFeedbackInLabel) { + container.classList.add('force-display'); + this.element.nativeElement.parentNode.insertAdjacentElement('afterEnd', container); + } else { + this.element.nativeElement.insertAdjacentElement('afterEnd', container); + } + } + } else { + this.element.nativeElement.classList.remove('is-invalid'); + if (isInvalidFeedbackContainer(errorContainerElement)) { + removeDOMNode(errorContainerElement); + } + } + } + + private addErrorMessages(container: HTMLElement): void { + this.errors.serverError.forEach((e: ErrorDescriptor) => { + const msg = document.createElement('div'); + this.subs.push(this.translation.stream(e.code, e.arguments).subscribe(text => { + msg.textContent = text; + })); + container.appendChild(msg); + }); + } + +} + +function isInvalidFeedbackContainer(container: HTMLElement): boolean { + return container && container.classList.contains('invalid-feedback'); +} + + + +// <div class="invalid-feedback" *ngIf="contactAndTicketsForm.get('firstName').errors?.serverError"> +// <div *ngFor="let err of contactAndTicketsForm.get('firstName').errors.serverError" [translate]="err"></div> +// </div> + diff --git a/frontend/projects/public/src/app/shared/language-selector/language-selector.component.html b/frontend/projects/public/src/app/shared/language-selector/language-selector.component.html new file mode 100644 index 0000000000..6ef09d8516 --- /dev/null +++ b/frontend/projects/public/src/app/shared/language-selector/language-selector.component.html @@ -0,0 +1,8 @@ +<div class="text-end" ngbDropdown *ngIf="filteredLanguages && filteredLanguages.length > 0"> + <button class="btn btn-link text-muted" id="languageDropdown" ngbDropdownToggle> + <fa-icon [icon]="['fas', 'globe']" aria-hidden="true" [title]="'reservation-page-complete.language' | translate"></fa-icon> {{currentLanguage}} + </button> + <div ngbDropdownMenu aria-labelledby="languageDropdown"> + <button ngbDropdownItem *ngFor="let lang of filteredLanguages" (click)="changeLanguage(lang.locale)" class="btn btn-link">{{lang.displayLanguage}}</button> + </div> +</div> diff --git a/frontend/projects/public/src/app/shared/language-selector/language-selector.component.ts b/frontend/projects/public/src/app/shared/language-selector/language-selector.component.ts new file mode 100644 index 0000000000..efdfc48e41 --- /dev/null +++ b/frontend/projects/public/src/app/shared/language-selector/language-selector.component.ts @@ -0,0 +1,66 @@ +import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core'; +import {Language} from '../../model/event'; +import {Router} from '@angular/router'; +import {I18nService} from '../i18n.service'; +import {Observable} from 'rxjs'; + +@Component({ + selector: 'app-language-selector', + templateUrl: './language-selector.component.html' +}) +export class LanguageSelectorComponent implements OnInit, OnChanges { + + @Input() + contentLanguages: Language[]; + private selectedLanguage: string; + private initialized = false; + currentLanguage: string; + filteredLanguages: Language[]; + + constructor(private i18nService: I18nService, private router: Router) { } + + ngOnInit(): void { + this.selectedLanguage = this.i18nService.getCurrentLang(); + this.buildValues(); + this.initialized = true; + } + + ngOnChanges(changes: SimpleChanges): void { + if (this.initialized && changes.contentLanguages) { + this.buildValues(); + } + } + + + + private buildValues(): void { + if (this.contentLanguages != null && this.contentLanguages.length > 0) { + let currentLang = this.contentLanguages.find(cl => cl.locale === this.selectedLanguage); + const languageNotFound = currentLang == null; + if (languageNotFound) { + currentLang = this.contentLanguages[0]; + this.changeLanguage(currentLang.locale); + } + this.currentLanguage = currentLang ? currentLang.displayLanguage : this.contentLanguages[0].displayLanguage; + this.filteredLanguages = this.contentLanguages.filter(cl => cl !== currentLang) + .sort((a, b) => a.displayLanguage.toLowerCase() > b.displayLanguage.toLowerCase() ? 1 : -1); + } + } + + public changeLanguage(lang: string): void { + if (this.selectedLanguage === lang) { + return; + } + const eventShortName = this.router.routerState.snapshot.root.firstChild ? this.router.routerState.snapshot.root.firstChild.params['eventShortName'] : null; + let observable: Observable<boolean>; + if (eventShortName != null) { + observable = this.i18nService.useTranslation('event', eventShortName, lang); + } else { + observable = this.i18nService.useTranslationForRoot(lang); + } + observable.subscribe(() => { + this.selectedLanguage = this.i18nService.getCurrentLang(); + this.buildValues(); + }); + } +} diff --git a/frontend/projects/public/src/app/shared/purchase-context.service.ts b/frontend/projects/public/src/app/shared/purchase-context.service.ts new file mode 100644 index 0000000000..d8caa7dc7b --- /dev/null +++ b/frontend/projects/public/src/app/shared/purchase-context.service.ts @@ -0,0 +1,26 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {EventService} from './event.service'; +import {SubscriptionService} from './subscription.service'; +import {PurchaseContext} from '../model/purchase-context'; + +@Injectable({ + providedIn: 'root' +}) +export class PurchaseContextService { + + constructor(private eventService: EventService, private subscriptionService: SubscriptionService) { } + + + getContext(type: PurchaseContextType, publicIdentifier: string): Observable<PurchaseContext> { + if (type === 'event') { + return this.eventService.getEvent(publicIdentifier); + } else if (type === 'subscription') { + return this.subscriptionService.getSubscriptionById(publicIdentifier); + } else { + throw new Error(); + } + } +} + +export type PurchaseContextType = 'event' | 'subscription'; diff --git a/frontend/projects/public/src/app/shared/reservation.service.ts b/frontend/projects/public/src/app/shared/reservation.service.ts new file mode 100644 index 0000000000..49a3c0e80c --- /dev/null +++ b/frontend/projects/public/src/app/shared/reservation.service.ts @@ -0,0 +1,90 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {ReservationRequest} from '../model/reservation-request'; +import {ValidatedResponse} from '../model/validated-response'; +import {OverviewConfirmation} from '../model/overview-confirmation'; +import {ReservationInfo, ReservationStatusInfo} from '../model/reservation-info'; +import {ReservationPaymentResult} from '../model/reservation-payment-result'; +import {TransactionInitializationToken} from '../model/payment'; +import {DynamicDiscount} from '../model/event-code'; +import {PurchaseContextType} from './purchase-context.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ReservationService { + + constructor(private http: HttpClient) { } + + reserveTickets(eventShortName: string, reservation: ReservationRequest, lang: string): Observable<ValidatedResponse<string>> { + return this.http.post<ValidatedResponse<string>>(`/api/v2/public/event/${eventShortName}/reserve-tickets`, reservation, {params: {lang: lang}}); + } + + getReservationInfo(reservationId: string): Observable<ReservationInfo> { + return this.http.get<ReservationInfo>(`/api/v2/public/reservation/${reservationId}`); + } + + getReservationStatusInfo(reservationId: string): Observable<ReservationStatusInfo> { + return this.http.get<ReservationStatusInfo>(`/api/v2/public/reservation/${reservationId}/status`); + } + + cancelPendingReservation(reservationId: string): Observable<boolean> { + return this.http.delete<boolean>(`/api/v2/public/reservation/${reservationId}`); + } + + validateToOverview(reservationId: string, contactsAndTicket: any, lang: string, ignoreWarnings: boolean): Observable<ValidatedResponse<boolean>> { + const url = `/api/v2/public/reservation/${reservationId}/validate-to-overview`; + return this.http.post<ValidatedResponse<boolean>>(url, contactsAndTicket, {params: {lang: lang, ignoreWarnings: '' + ignoreWarnings}}); + } + + confirmOverview(reservationId: string, overviewForm: OverviewConfirmation, lang: string): Observable<ValidatedResponse<ReservationPaymentResult>> { + const url = `/api/v2/public/reservation/${reservationId}`; + return this.http.post<ValidatedResponse<ReservationPaymentResult>>(url, overviewForm, {params: {lang: lang}}); + } + + backToBooking(reservationId: string): Observable<boolean> { + return this.http.post<boolean>(`/api/v2/public/reservation/${reservationId}/back-to-booking`, {}); + } + + reSendReservationEmail(purchaseContextType: PurchaseContextType, publicIdentifier: string, reservationId: string, lang: string): Observable<boolean> { + return this.http.post<boolean>(`/api/v2/public/${purchaseContextType}/${publicIdentifier}/reservation/${reservationId}/re-send-email`, {}, {params: {lang: lang}}); + } + + initPayment(reservationId: string): Observable<TransactionInitializationToken> { + return this.http.post<TransactionInitializationToken>(`/api/v2/public/reservation/${reservationId}/payment/CREDIT_CARD/init`, {}); + } + + getPaymentStatus(reservationId: string): Observable<ReservationPaymentResult> { + return this.http.get<ReservationPaymentResult>(`/api/v2/public/reservation/${reservationId}/payment/CREDIT_CARD/status`); + } + + forcePaymentStatusCheck(reservationId: string): Observable<ReservationPaymentResult> { + return this.http.get<ReservationPaymentResult>(`/api/v2/public/reservation/${reservationId}/transaction/force-check`); + } + + removePaymentToken(reservationId: string): Observable<boolean> { + return this.http.delete<boolean>(`/api/v2/public/reservation/${reservationId}/payment/token`); + } + + resetPaymentStatus(reservationId: string): Observable<boolean> { + return this.http.delete<boolean>(`/api/v2/public/reservation/${reservationId}/payment`); + } + + registerPaymentAttempt(reservationId: string): Observable<boolean> { + return this.http.put<boolean>(`/api/v2/public/reservation/${reservationId}/payment`, {}); + } + + checkDynamicDiscountAvailability(eventShortName: string, reservation: ReservationRequest): Observable<DynamicDiscount> { + return this.http.post<DynamicDiscount>(`/api/v2/public/event/${eventShortName}/check-discount`, reservation); + } + + applySubscriptionCode(reservationId: string, code: string, email: string): Observable<ValidatedResponse<boolean>> { + return this.http.post<ValidatedResponse<boolean>>(`/api/v2/public/reservation/${reservationId}/apply-code/`, {code: code, email: email, amount: 1, type: 'SUBSCRIPTION'}); + } + + removeSubscription(reservationId: string): Observable<boolean> { + return this.http.delete<boolean>(`/api/v2/public/reservation/${reservationId}/remove-code`, {params: {type: 'SUBSCRIPTION'}}); + } + +} diff --git a/frontend/projects/public/src/app/shared/shared.module.ts b/frontend/projects/public/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..f1e049590f --- /dev/null +++ b/frontend/projects/public/src/app/shared/shared.module.ts @@ -0,0 +1,43 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {TranslateModule} from '@ngx-translate/core'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {NgbDropdownModule, NgbToastModule} from '@ng-bootstrap/ng-bootstrap'; + +import {LanguageSelectorComponent} from './language-selector/language-selector.component'; +import {PurchaseContextHeaderComponent} from './event-header/purchase-context-header.component'; +import {InvalidFeedbackDirective} from './invalid-feedback.directive'; +import {ClipboardCopyDirective} from './clipboard-copy/clipboard-copy.directive'; +import {FeedbackComponent} from './feedback/feedback.component'; +import {WarningModalComponent} from './warning-modal/warning-modal.component'; +import {TopBarComponent} from './topbar/top-bar.component'; + +@NgModule({ + declarations: [ + LanguageSelectorComponent, + PurchaseContextHeaderComponent, + InvalidFeedbackDirective, + ClipboardCopyDirective, + FeedbackComponent, + WarningModalComponent, + TopBarComponent + ], + imports: [ + CommonModule, + TranslateModule.forChild(), + FontAwesomeModule, + NgbDropdownModule, + NgbToastModule + ], + exports: [ + LanguageSelectorComponent, + PurchaseContextHeaderComponent, + InvalidFeedbackDirective, + ClipboardCopyDirective, + FeedbackComponent, + WarningModalComponent, + TopBarComponent + ], +}) +export class SharedModule { } diff --git a/frontend/projects/public/src/app/shared/subscription.service.ts b/frontend/projects/public/src/app/shared/subscription.service.ts new file mode 100644 index 0000000000..b9fac264f0 --- /dev/null +++ b/frontend/projects/public/src/app/shared/subscription.service.ts @@ -0,0 +1,48 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {BasicSubscriptionInfo, SubscriptionInfo} from '../model/subscription'; +import {ValidatedResponse} from '../model/validated-response'; +import {shareReplay} from 'rxjs/operators'; +import {SearchParams} from '../model/search-params'; + +@Injectable({ + providedIn: 'root' +}) +export class SubscriptionService { + + + private subscriptionCache: {[key: string]: Observable<SubscriptionInfo>} = {}; + + constructor(private http: HttpClient) { } + + getSubscriptions(searchParams?: SearchParams): Observable<BasicSubscriptionInfo[]> { + const params = searchParams?.toHttpParams(); + return this.http.get<BasicSubscriptionInfo[]>('/api/v2/public/subscriptions', { + responseType: 'json', + params + }); + } + + getSubscriptionById(id: string): Observable<SubscriptionInfo> { + if (!this.subscriptionCache[id]) { + this.subscriptionCache[id] = this.http.get<SubscriptionInfo>(`/api/v2/public/subscription/${id}`).pipe(shareReplay(1)); + setTimeout(() => { + delete this.subscriptionCache[id]; + }, 60000 * 20); // clean up cache after 20 minutes + } + return this.subscriptionCache[id]; + } + + reserve(id: string): Observable<ValidatedResponse<string>> { + return this.http.post<ValidatedResponse<string>>(`/api/v2/public/subscription/${id}`, {}); + } +} + +export function getLocalizedContent(container: {[k: string]: string}, currentLang: string) { + const localizedContent = container[currentLang]; + if (localizedContent != null) { + return localizedContent; + } + return container[Object.keys(container)[0]]; +} diff --git a/frontend/projects/public/src/app/shared/ticket.service.ts b/frontend/projects/public/src/app/shared/ticket.service.ts new file mode 100644 index 0000000000..09ec6bebdf --- /dev/null +++ b/frontend/projects/public/src/app/shared/ticket.service.ts @@ -0,0 +1,135 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {from, Observable, of} from 'rxjs'; +import {TicketInfo} from '../model/ticket-info'; +import {UntypedFormBuilder, UntypedFormGroup} from '@angular/forms'; +import {AdditionalField, Ticket} from '../model/ticket'; +import {ValidatedResponse} from '../model/validated-response'; +import {TicketsByTicketCategory} from '../model/reservation-info'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ReleaseTicketComponent} from '../reservation/release-ticket/release-ticket.component'; +import {map, mergeMap} from 'rxjs/operators'; +import {User, UserAdditionalData} from '../model/user'; +import {DateValidity} from '../model/date-validity'; +import {DownloadTicketComponent} from '../reservation/download-ticket/download-ticket.component'; +import {WalletConfiguration} from '../model/info'; + +@Injectable({ + providedIn: 'root' +}) +export class TicketService { + + private static getUserDataLabelValue(name: string, index: number, userLanguage?: string, additionalData?: UserAdditionalData): {l: string, v: string} | null { + if (additionalData != null && additionalData[name] && additionalData[name].values.length > index) { + const val = additionalData[name]; + return { l: val.label[userLanguage] || val.label[0], v: val.values[index] }; + } + return { l: null, v: null }; + } + + constructor( + private http: HttpClient, + private formBuilder: UntypedFormBuilder, + private modalService: NgbModal) { } + + getTicketInfo(eventName: string, ticketIdentifier: string): Observable<TicketInfo> { + return this.http.get<TicketInfo>(`/api/v2/public/event/${eventName}/ticket/${ticketIdentifier}`); + } + + getTicket(eventName: string, ticketIdentifier: string): Observable<TicketsByTicketCategory> { + return this.http.get<TicketsByTicketCategory>(`/api/v2/public/event/${eventName}/ticket/${ticketIdentifier}/full`); + } + + getOnlineCheckInInfo(eventName: string, ticketIdentifier: string, checkInCode: string): Observable<DateValidity> { + return this.http.get<DateValidity>(`/api/v2/public/event/${eventName}/ticket/${ticketIdentifier}/code/${checkInCode}/check-in-info`, { + params: { + tz: this.retrieveTimezone() + } + }); + } + + sendTicketByEmail(eventName: string, ticketIdentifier: string): Observable<boolean> { + return this.http.post<boolean>(`/api/v2/public/event/${eventName}/ticket/${ticketIdentifier}/send-ticket-by-email`, {}); + } + + buildFormGroupForTicket(ticket: Ticket, user?: User): UntypedFormGroup { + return this.formBuilder.group(this.buildTicket(ticket, user)); + } + + + updateTicket(eventName: string, ticketIdentifier: string, ticket: any): Observable<ValidatedResponse<boolean>> { + return this.http.put<ValidatedResponse<boolean>>(`/api/v2/public/event/${eventName}/ticket/${ticketIdentifier}`, ticket); + } + + openReleaseTicket(ticket: Ticket, eventName: string): Observable<boolean> { + return from(this.modalService.open(ReleaseTicketComponent, {centered: true}).result) + .pipe(mergeMap(res => { + if (res === 'yes') { + return this.releaseTicket(eventName, ticket.uuid); + } else { + return of(false); + } + })); + } + + openDownloadTicket(ticket: Ticket, eventName: string, walletConfiguration: WalletConfiguration): Observable<boolean> { + const modal = this.modalService.open(DownloadTicketComponent, {centered: true}); + const instance: DownloadTicketComponent = modal.componentInstance; + instance.ticket = ticket; + instance.eventName = eventName; + instance.walletConfiguration = walletConfiguration; + return from(modal.result) + .pipe(map(_ => true)); + } + + releaseTicket(eventName: string, ticketIdentifier: string): Observable<boolean> { + return this.http.delete<boolean>(`/api/v2/public/event/${eventName}/ticket/${ticketIdentifier}`, {}); + } + + private buildTicket(ticket: Ticket, user?: User): {firstName: string, lastName: string, email: string, userLanguage, additional: UntypedFormGroup} { + return { + firstName: ticket.firstName || user?.firstName, + lastName: ticket.lastName || user?.lastName, + email: ticket.email || user?.emailAddress, + userLanguage: ticket.userLanguage, + additional: this.buildAdditionalFields(ticket.ticketFieldConfigurationBeforeStandard, + ticket.ticketFieldConfigurationAfterStandard, ticket.userLanguage, user?.profile?.additionalData) + }; + } + + private buildAdditionalFields(before: AdditionalField[], after: AdditionalField[], userLanguage: string, userData?: UserAdditionalData): UntypedFormGroup { + const additional = {}; + if (before) { + this.buildSingleAdditionalField(before, additional, userLanguage, userData); + } + if (after) { + this.buildSingleAdditionalField(after, additional, userLanguage, userData); + } + return this.formBuilder.group(additional); + } + + private buildSingleAdditionalField(a: AdditionalField[], additional: {}, userLanguage: string, userData?: UserAdditionalData): void { + a.forEach(f => { + const arr = []; + + if (f.type === 'checkbox') { // pre-fill with empty values for the checkbox cases, as we can have multiple values! + for (let i = 0; i < f.restrictedValues.length; i++) { + arr.push(this.formBuilder.control(null)); + } + } + + f.fields.forEach(field => { + arr[field.fieldIndex] = this.formBuilder.control(field.fieldValue || TicketService.getUserDataLabelValue(f.name, field.fieldIndex, userLanguage, userData).v); + }); + additional[f.name] = this.formBuilder.array(arr); + }); + } + + private retrieveTimezone(): string | null { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch (e) { + return null; + } + } +} diff --git a/frontend/projects/public/src/app/shared/topbar/top-bar.component.html b/frontend/projects/public/src/app/shared/topbar/top-bar.component.html new file mode 100644 index 0000000000..c5641af6d7 --- /dev/null +++ b/frontend/projects/public/src/app/shared/topbar/top-bar.component.html @@ -0,0 +1,25 @@ +<div class="d-flex justify-content-end"> + <div> + <app-language-selector [contentLanguages]="contentLanguages"></app-language-selector> + </div> + <div class="ms-4" *ngIf="authenticationEnabled"> + <div *ngIf="user && !displayLoginButton" class="text-muted"> + <fa-icon [icon]="['fas', 'user-astronaut']" aria-hidden="true" [title]="'user.menu.title' | translate"></fa-icon> {{user?.firstName}} {{user?.lastName}} + </div> + <div ngbDropdown *ngIf="user && displayLoginButton"> + <button type="button" class="btn btn-link text-muted" id="userDropdown" ngbDropdownToggle> + <fa-icon [icon]="['fas', 'user-astronaut']" aria-hidden="true" [title]="'user.menu.title' | translate"></fa-icon> {{user?.firstName}} {{user?.lastName}} + </button> + <div ngbDropdownMenu aria-labelledby="userDropdown"> + <button type="button" ngbDropdownItem class="btn btn-link" (click)="myProfile()"><fa-icon [icon]="['fas', 'user-astronaut']" a11yRole="presentation"></fa-icon> <span class="ms-3">{{ 'user.menu.my-profile' | translate }}</span></button> + <button type="button" ngbDropdownItem class="btn btn-link" (click)="myOrders()"><fa-icon [icon]="['fas', 'ticket-alt']" [classes]="['rotate-45']" a11yRole="presentation"></fa-icon> <span class="ms-3">{{ 'user.menu.my-orders' | translate }}</span></button> + <button type="button" ngbDropdownItem class="btn btn-link" (click)="logout()"><fa-icon [icon]="['fas', 'sign-out-alt']" a11yRole="presentation"></fa-icon> <span class="ms-3">{{ 'user.menu.logout' | translate }}</span></button> + </div> + </div> + <div *ngIf="!user && displayLoginButton"> + <a href="/openid/authentication" class="btn btn-link text-muted"> + <fa-icon [icon]="['fas', 'sign-in-alt']" a11yRole="presentation"></fa-icon> {{ 'user.menu.login' | translate }} + </a> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/shared/topbar/top-bar.component.ts b/frontend/projects/public/src/app/shared/topbar/top-bar.component.ts new file mode 100644 index 0000000000..1e1a9622ad --- /dev/null +++ b/frontend/projects/public/src/app/shared/topbar/top-bar.component.ts @@ -0,0 +1,73 @@ +import {AfterViewInit, Component, Input, OnDestroy, OnInit} from '@angular/core'; +import {UserService} from '../user.service'; +import {ANONYMOUS, User} from '../../model/user'; +import {Language} from '../../model/event'; +import {Subscription} from 'rxjs'; +import {Router} from '@angular/router'; +import {FeedbackService} from '../feedback/feedback.service'; +import {DELETE_ACCOUNT_CONFIRMATION, getFromSessionStorage, removeFromSessionStorage} from '../util'; + +@Component({ + selector: 'app-topbar', + templateUrl: './top-bar.component.html' +}) +export class TopBarComponent implements OnInit, OnDestroy, AfterViewInit { + + private authenticationStatusSubscription?: Subscription; + @Input() + contentLanguages: Language[]; + @Input() + displayLoginButton = true; + user?: User; + authenticationEnabled = false; + private root = document.querySelector(':root') as HTMLElement; + + constructor(private userService: UserService, + private router: Router, + private feedbackService: FeedbackService) { + } + + ngOnInit(): void { + this.authenticationStatusSubscription = this.userService.authenticationStatus.subscribe(authenticationStatus => { + this.authenticationEnabled = authenticationStatus.enabled; + if (authenticationStatus.user !== ANONYMOUS) { + this.user = authenticationStatus.user; + } + }); + } + + ngAfterViewInit(): void { + if (getFromSessionStorage(DELETE_ACCOUNT_CONFIRMATION) === 'y') { + this.feedbackService.showSuccess('my-profile.delete.success'); + removeFromSessionStorage(DELETE_ACCOUNT_CONFIRMATION); + } else if (this.root != null && this.root.getAttribute('data-signed-up') != null) { + this.feedbackService.showSuccess('my-profile.sign-up.success'); + this.root.removeAttribute('data-signed-up'); + } + } + + ngOnDestroy(): void { + this.authenticationStatusSubscription?.unsubscribe(); + } + + get anonymous(): boolean { + return this.user === ANONYMOUS; + } + + logout(): void { + this.userService.logout().subscribe(response => { + this.user = undefined; + if (!response.empty) { + window.location.href = response.targetUrl; + } + }); + } + + myOrders(): void { + this.router.navigate(['my-orders']); + } + + myProfile(): void { + this.router.navigate(['my-profile']); + } +} diff --git a/frontend/projects/public/src/app/shared/translate-description.pipe.ts b/frontend/projects/public/src/app/shared/translate-description.pipe.ts new file mode 100644 index 0000000000..d3be95cf6e --- /dev/null +++ b/frontend/projects/public/src/app/shared/translate-description.pipe.ts @@ -0,0 +1,19 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; +import {getLocalizedContent} from './subscription.service'; + +@Pipe({ + name: 'translateDescription' +}) +export class TranslateDescriptionPipe implements PipeTransform { + + constructor(private translate: TranslateService) {} + + transform(value?: {[k: string]: string}): any { + if (value != null) { + const lang = this.translate.currentLang; + return getLocalizedContent(value, lang); + } + return null; + } +} diff --git a/frontend/projects/public/src/app/shared/user.service.ts b/frontend/projects/public/src/app/shared/user.service.ts new file mode 100644 index 0000000000..6483be9f58 --- /dev/null +++ b/frontend/projects/public/src/app/shared/user.service.ts @@ -0,0 +1,91 @@ +import {Injectable, OnDestroy} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {ANONYMOUS, AuthenticationStatus, ClientRedirect, PurchaseContextWithReservation, User} from '../model/user'; +import {BehaviorSubject, interval, Observable, of, Subscription} from 'rxjs'; +import {map, mergeMap, tap} from 'rxjs/operators'; +import {ValidatedResponse} from '../model/validated-response'; + +@Injectable({ providedIn: 'root' }) +export class UserService implements OnDestroy { + + private authStatusSubject = new BehaviorSubject<AuthenticationStatus>({ enabled: false }); + private authStatusSubscription?: Subscription; + private authEnabled = false; + + constructor(private http: HttpClient) { + } + + initAuthenticationStatus(): Promise<boolean> { + return new Promise<boolean>((resolve) => this.loadUserStatus() + .subscribe(result => { + this.authStatusSubject.next(result); + resolve(true); + }, () => { + this.authStatusSubject.next({ enabled: false }); + resolve(true); // we resolve the promise anyway + })); + } + + private loadUserStatus(): Observable<{enabled: boolean, user?: User}> { + return this.http.get<boolean>('/api/v2/public/user/authentication-enabled') + .pipe(mergeMap(enabled => { + if (enabled) { + return this.getUserIdentity().pipe(map(user => ({enabled, user}))); + } + return of({enabled, user: undefined}); + }), + tap(status => this.authEnabled = status.enabled) + ); + } + + public getUserIdentity(): Observable<User> { + return this.http.get<User>('/api/v2/public/user/me', { observe: 'response' }) + .pipe(map(response => { + if (response.status === 204) { + return ANONYMOUS; + } else { + return response.body; + } + })); + } + + ngOnDestroy(): void { + this.authStatusSubscription?.unsubscribe(); + } + + logout(): Observable<ClientRedirect> { + return this.http.post<ClientRedirect>('/api/v2/public/user/logout', {}) + .pipe(tap(() => { + this.authStatusSubject.next({ enabled: true }); + })); + } + + getOrders(): Observable<Array<PurchaseContextWithReservation>> { + return this.http.get<Array<PurchaseContextWithReservation>>('/api/v2/public/user/reservations'); + } + + updateUser(user: any): Observable<ValidatedResponse<User>> { + return this.http.post<ValidatedResponse<User>>('/api/v2/public/user/me', user); + } + + private startPolling(): void { + this.authStatusSubscription = interval(30_000).pipe( + mergeMap(() => this.loadUserStatus()) + ).subscribe(() => {}); + } + + get authenticationStatus(): Observable<AuthenticationStatus> { + if (this.authStatusSubscription == null) { + this.startPolling(); + } + return this.authStatusSubject.asObservable(); + } + + deleteProfile(): Observable<ClientRedirect> { + return this.http.delete<ClientRedirect>('/api/v2/public/user/me').pipe( + tap(() => { + this.authStatusSubject.next({ enabled: true }); + }) + ); + } +} diff --git a/frontend/projects/public/src/app/shared/util.ts b/frontend/projects/public/src/app/shared/util.ts new file mode 100644 index 0000000000..311d09b2df --- /dev/null +++ b/frontend/projects/public/src/app/shared/util.ts @@ -0,0 +1,69 @@ +import {ReservationStatusChanged} from '../model/embedding-configuration'; +import {PurchaseContext} from '../model/purchase-context'; +import {ReservationInfo, ReservationStatus} from '../model/reservation-info'; +import {HttpErrorResponse} from '@angular/common/http'; +import {ReservationService} from './reservation.service'; +import {interval} from 'rxjs'; +import {filter, mergeMap} from 'rxjs/operators'; + +export const DELETE_ACCOUNT_CONFIRMATION = 'alfio.delete-account.confirmation'; + +export function writeToSessionStorage(key: string, value: string): void { + try { + window.sessionStorage.setItem(key, value); + } catch (e) { + // session storage might be disabled in some contexts + } +} + +export function getFromSessionStorage(key: string): string | null { + try { + return window.sessionStorage.getItem(key); + } catch (e) { + // session storage might be disabled in some contexts + return null; + } +} + +export function removeFromSessionStorage(key: string): void { + try { + window.sessionStorage.removeItem(key); + } catch (e) { + } +} + +export const mobile = window.matchMedia('(max-width: 767px)').matches; +export const embedded = window.parent !== window; + +export function notifyPaymentErrorToParent(purchaseContext: PurchaseContext, + reservationInfo: ReservationInfo, + reservationId: string, + err: Error) { + if (embedded && purchaseContext.embeddingConfiguration.enabled) { + window.parent.postMessage( + new ReservationStatusChanged(reservationInfo.status, reservationId, errorMessage(err)), + purchaseContext.embeddingConfiguration.notificationOrigin + ); + } +} + +export function pollReservationStatus(reservationId: string, + reservationService: ReservationService, + processSuccessful: (res: ReservationInfo) => void, + desiredStatuses: Array<ReservationStatus> = ['COMPLETE']): void { + const subscription = interval(5000) + .pipe( + mergeMap(() => reservationService.getReservationInfo(reservationId)), + filter(reservationInfo => desiredStatuses.includes(reservationInfo.status)) + ).subscribe(reservationInfo => { + processSuccessful(reservationInfo); + subscription.unsubscribe(); + }); +} + +function errorMessage(err: Error): string { + if (err instanceof HttpErrorResponse) { + return `${err.message} (${err.status})`; + } + return err.message; +} diff --git a/frontend/projects/public/src/app/shared/validation-helper.ts b/frontend/projects/public/src/app/shared/validation-helper.ts new file mode 100644 index 0000000000..9200d6dd1e --- /dev/null +++ b/frontend/projects/public/src/app/shared/validation-helper.ts @@ -0,0 +1,78 @@ +import {AbstractControl} from '@angular/forms'; +import {ErrorDescriptor, ValidatedResponse} from '../model/validated-response'; +import {HttpErrorResponse} from '@angular/common/http'; + +function applyValidationErrors(form: AbstractControl, response: ValidatedResponse<any>): ErrorDescriptor[] { + + if (response.errorCount === 0) { + return []; + } + + const globalErrors: ErrorDescriptor[] = []; + + response.validationErrors.forEach(err => { + + // form.get('tickets[207f224c-1df6-4994-9cb2-fa12eb36882d].email') -> not ok + // form.get('tickets.207f224c-1df6-4994-9cb2-fa12eb36882d.email') -> ok + const transformedFieldName = err.fieldName.replace(/\[/g, '.').replace(/\]/g, ''); + + const formControl = form.get(transformedFieldName); + + if (formControl) { + const formControlErr = formControl.getError('serverError'); + if (formControlErr) { + const errors = (formControlErr as ErrorDescriptor[]); + if (!containsWithKey(errors, err.code)) { + errors.push(err); + } + } else { + formControl.setErrors({serverError: [err]}); + } + formControl.markAsTouched(); + } else { + globalErrors.push(err); + } + }); + + // TODO: find better way -> this focus and scroll on the first invalid form input + setTimeout(() => { + // meh, should find a better way + const found = document.querySelectorAll('[appinvalidfeedback].ng-invalid, [appinvalidfeedback].is-invalid'); + if (found && found.length > 0) { + const elem = found[0] as HTMLElement; + window.scroll({ + behavior: 'smooth', + left: 0, + top: window.scrollY + elem.getBoundingClientRect().top - 100 + }); + elem.focus({ preventScroll: true }); + } + }, 10); + return globalErrors; +} + +function containsWithKey(errors: ErrorDescriptor[], key: string) { + for (let i = 0; i < errors.length; i++) { + if (errors[i].code === key) { + return true; + } + } + return false; +} + +export function handleServerSideValidationError(err: any, form: AbstractControl): ErrorDescriptor[] { + const errorObject = getErrorObject(err); + if (errorObject != null) { + return applyValidationErrors(form, errorObject); + } + return []; +} + +export function getErrorObject(err: any): ValidatedResponse<any> | null { + if (err instanceof ValidatedResponse) { + return err; + } else if (err instanceof HttpErrorResponse && err.status === 422) { + return err.error; + } + return null; +} diff --git a/frontend/projects/public/src/app/shared/warning-modal/warning-modal.component.html b/frontend/projects/public/src/app/shared/warning-modal/warning-modal.component.html new file mode 100644 index 0000000000..f97da33fee --- /dev/null +++ b/frontend/projects/public/src/app/shared/warning-modal/warning-modal.component.html @@ -0,0 +1,17 @@ +<div class="modal-body text-center"> + <div class="text-warning mb-3"> + <fa-icon [icon]="['fas', 'exclamation-triangle']" [size]="'3x'" a11yRole="presentation"></fa-icon> + </div> + <h4 class="modal-title text-center"> + {{ message | translate:parameters}} + </h4> + <hr/> + <div class="row"> + <div class="col-6"> + <button type="button" class="btn btn-success block-button" (click)="activeModal.dismiss()" translate="warning.check"></button> + </div> + <div class="col-6"> + <button type="button" class="btn btn-light block-button" (click)="activeModal.close()" translate="warning.proceed"></button> + </div> + </div> +</div> diff --git a/frontend/projects/public/src/app/shared/warning-modal/warning-modal.component.ts b/frontend/projects/public/src/app/shared/warning-modal/warning-modal.component.ts new file mode 100644 index 0000000000..6fd1eb307f --- /dev/null +++ b/frontend/projects/public/src/app/shared/warning-modal/warning-modal.component.ts @@ -0,0 +1,17 @@ +import {Component, Input} from '@angular/core'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-warning-modal', + templateUrl: './warning-modal.component.html' +}) +export class WarningModalComponent { + + @Input() + message: string; + + @Input() + parameters: {[key: string]: string}; + + constructor(public activeModal: NgbActiveModal) {} +} diff --git a/frontend/projects/public/src/app/stepper/stepper.component.html b/frontend/projects/public/src/app/stepper/stepper.component.html new file mode 100644 index 0000000000..5142c194ed --- /dev/null +++ b/frontend/projects/public/src/app/stepper/stepper.component.html @@ -0,0 +1,32 @@ +<div class="wizard2 center text-muted"> + <div [ngClass]="{'wizard2-current': currentStep === 1}" [attr.aria-hidden]="currentStep !== 1"> + <div> + <span class="badge" [ngClass]="{'badge-success': currentStep > 1 }"><fa-icon [icon]="['fas', 'check']" a11yRole="presentation"></fa-icon></span> + <span translate="breadcrumb.step1"></span> + </div> + <div class="wizard2-line"></div> + </div> + <div [ngClass]="{'wizard2-current': currentStep === 2}" [attr.aria-hidden]="currentStep !== 2"> + <div> + <span class="badge" [ngClass]="{'badge-success': currentStep > 2 }"><fa-icon [icon]="currentStep > 2 ? ['fas', 'check'] : ['fas', 'address-card']" a11yRole="presentation"></fa-icon></span> + <span translate="breadcrumb.step2"></span> + </div> + <div class="wizard2-line"></div> + </div> + <div [ngClass]="{'wizard2-current': currentStep === 3}" [attr.aria-hidden]="currentStep !== 3"> + <div> + <span class="badge" [ngClass]="{'badge-success': currentStep > 3 }"> + <fa-icon *ngIf="free && !inProgress" [icon]="currentStep > 3 ? ['fas', 'check'] : ['fas', 'file-alt']" a11yRole="presentation"></fa-icon> + <fa-icon *ngIf="!free && !inProgress" [icon]="currentStep > 3 ? ['fas', 'check'] : ['fas', 'money-bill']" a11yRole="presentation"></fa-icon> + <fa-icon *ngIf="inProgress" [icon]="['fas', 'cog']" [spin]="true" a11yRole="presentation"></fa-icon> + </span> + <span [translate]="'breadcrumb.step3'+(free ? '.free' : '')"></span></div> + <div class="wizard2-line"></div> + </div> + <div [ngClass]="{'wizard2-current': currentStep === 4}" [attr.aria-hidden]="currentStep !== 4"> + <div> + <span class="badge" [ngClass]="{'badge-success': currentStep > 4 }"><fa-icon [icon]="['fas', 'thumbs-up']" a11yRole="presentation"></fa-icon></span> + <span translate="breadcrumb.step4"></span> + </div> + </div> +</div> \ No newline at end of file diff --git a/frontend/projects/public/src/app/stepper/stepper.component.scss b/frontend/projects/public/src/app/stepper/stepper.component.scss new file mode 100644 index 0000000000..0749e1c366 --- /dev/null +++ b/frontend/projects/public/src/app/stepper/stepper.component.scss @@ -0,0 +1,60 @@ +/* based on https://material-ui.com/demos/steppers/ */ +.wizard2 { + display: flex; + flex-direction: row; + align-items: flex-start; + margin: 4rem 0; +} + +.wizard2 > div { + flex: 1; + position:relative +} + +.wizard2 > div > div:first-child { + flex-direction: column; + display: flex; + align-items: center; +} + +.wizard2 .badge { + display: inline-block; + min-width: 35px; + padding: 3px 7px; + font-size: 14px; + font-weight: 700; + line-height: 2; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: top; + background-color: #777; + border-radius: 17px; + min-height: 30px; +} + +.wizard2 .wizard2-line { + top: 17px; + left: calc(50% + 20px); + right: calc(-50% + 20px); + position: absolute; + border-top: 1px solid #bdbdbd; +} + +.badge-success { + background-color: #449d44 !important; +} + +.wizard2 .wizard2-current .badge { + background-color: #007ACC; +} + +.wizard2 span:nth-child(2) { + margin-top: 1em; + text-align: center; +} + +.wizard2 .wizard2-current span:nth-child(2) { + font-weight:bold; + color: black; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/stepper/stepper.component.ts b/frontend/projects/public/src/app/stepper/stepper.component.ts new file mode 100644 index 0000000000..841be0c734 --- /dev/null +++ b/frontend/projects/public/src/app/stepper/stepper.component.ts @@ -0,0 +1,20 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'app-stepper', + templateUrl: './stepper.component.html', + styleUrls: ['./stepper.component.scss'] +}) +export class StepperComponent { + + @Input() + free = true; + + @Input() + currentStep = 1; + + @Input() + inProgress = false; + + constructor() { } +} diff --git a/frontend/projects/public/src/app/subscription-display/subscription-display.component.html b/frontend/projects/public/src/app/subscription-display/subscription-display.component.html new file mode 100644 index 0000000000..4508331ba1 --- /dev/null +++ b/frontend/projects/public/src/app/subscription-display/subscription-display.component.html @@ -0,0 +1,30 @@ +<app-purchase-context-container [purchaseContext]="subscription"> + <div *ngIf="subscription" class="add-margin-bottom"> + <header> + <app-purchase-context-header [purchaseContext]="subscription" type="subscription"></app-purchase-context-header> + </header> + <main> + <hr> + <h2>{{ subscription.title[translateService.currentLang] }}</h2> + <div class="mt-3"> + <app-subscription-summary [subscription]="subscription"></app-subscription-summary> + </div> + <hr> + <form (submit)="submitForm()"> + + <div class="markdown-content" [innerHTML]="subscription.description[translateService.currentLang]"></div> + + <div class="alert alert-warning text-center" role="alert" *ngIf="subscription.numAvailable == 0"> + <h3 translate="show-subscription.sold-out.message"></h3> + </div> + + <hr class="mt-5"> + + <div class="row d-flex justify-content-between mobile-add-margin-bottom"> + <div class="col-md-5 order-md-1 col-12"><button type="submit" *ngIf="subscription.numAvailable > 0" class="block-button btn btn-success" translate="show-event.continue"></button></div> + <div class="col-md-5 order-md-0 col-12 "><a [href]="subscription.websiteUrl" class="block-button btn btn-light" translate="common.back-to-organizer"></a></div> + </div> + </form> + </main> + </div> +</app-purchase-context-container> diff --git a/frontend/projects/public/src/app/subscription-display/subscription-display.component.scss b/frontend/projects/public/src/app/subscription-display/subscription-display.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/public/src/app/subscription-display/subscription-display.component.ts b/frontend/projects/public/src/app/subscription-display/subscription-display.component.ts new file mode 100644 index 0000000000..fe978ad1cf --- /dev/null +++ b/frontend/projects/public/src/app/subscription-display/subscription-display.component.ts @@ -0,0 +1,62 @@ +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {SubscriptionInfo} from '../model/subscription'; +import {AnalyticsService} from '../shared/analytics.service'; +import {InfoService} from '../shared/info.service'; +import {SubscriptionService} from '../shared/subscription.service'; +import {zip} from 'rxjs'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../shared/i18n.service'; +import {getErrorObject} from '../shared/validation-helper'; +import {ErrorDescriptor} from '../model/validated-response'; +import {FeedbackService} from '../shared/feedback/feedback.service'; + +@Component({ + selector: 'app-subscription-display', + templateUrl: './subscription-display.component.html', + styleUrls: ['./subscription-display.component.scss'] +}) +export class SubscriptionDisplayComponent implements OnInit { + + + submitError: ErrorDescriptor; + subscription: SubscriptionInfo; + subscriptionId: string; + + constructor(private route: ActivatedRoute, + private router: Router, + private subscriptionService: SubscriptionService, + private info: InfoService, + private analytics: AnalyticsService, + public translateService: TranslateService, + private i18nService: I18nService, + private feedbackService: FeedbackService) { } + + ngOnInit(): void { + this.route.params.subscribe(params => { + this.subscriptionId = params['id']; + zip(this.subscriptionService.getSubscriptionById(this.subscriptionId), this.info.getInfo()).subscribe(([subscription, info]) => { + this.subscription = subscription; + this.i18nService.setPageTitle('show-subscription.header.title', subscription); + this.analytics.pageView(info.analyticsConfiguration); + }); + }); + } + + + submitForm() { + this.subscriptionService.reserve(this.subscriptionId).subscribe(res => { + this.router.navigate(['subscription', this.subscriptionId, 'reservation', res.value, 'book']); + }, (err) => { + const errorObject = getErrorObject(err); + let errorCode: string; + if (errorObject != null) { + errorCode = errorObject.validationErrors[0].code; + } else { + errorCode = 'reservation-page-error-status.header.title'; + } + this.feedbackService.showError(errorCode); + }); + } + +} diff --git a/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.html b/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.html new file mode 100644 index 0000000000..f6ef8578e2 --- /dev/null +++ b/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.html @@ -0,0 +1,17 @@ +<div *ngIf="subscriptions" class="application-container p-2 p-md-5"> + <app-topbar [contentLanguages]="languages"></app-topbar> + + <h1 translate="subscription.title"></h1> + + + <div class="event-list-card mb-3" *ngFor="let subscription of subscriptions"> + <app-basic-subscription-info [subscription]="subscription" [params]="queryParams"></app-basic-subscription-info> + </div> + + <div class="alert text-center" *ngIf="subscriptions.length == 0"> + <h3 translate="subscription.no-subscriptions"></h3> + </div> + + <hr class="mt-5" *ngIf="linksContainer.termsAndConditionsUrl || linksContainer.privacyPolicyUrl"> + <app-footer-links [linksContainer]="linksContainer"></app-footer-links> +</div> diff --git a/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.scss b/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.ts b/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.ts new file mode 100644 index 0000000000..a4de278127 --- /dev/null +++ b/frontend/projects/public/src/app/subscription-list-all/subscription-list-all.component.ts @@ -0,0 +1,57 @@ +import {Component, OnInit} from '@angular/core'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {TranslateService} from '@ngx-translate/core'; +import {BasicSubscriptionInfo} from '../model/subscription'; +import {AnalyticsService} from '../shared/analytics.service'; +import {I18nService} from '../shared/i18n.service'; +import {InfoService} from '../shared/info.service'; +import {SubscriptionService} from '../shared/subscription.service'; +import {of, zip} from 'rxjs'; +import {Language, TermsPrivacyLinksContainer} from '../model/event'; +import {globalTermsPrivacyLinks} from '../model/info'; +import {filterAvailableLanguages} from '../model/purchase-context'; +import {mergeMap} from 'rxjs/operators'; +import {SearchParams} from '../model/search-params'; + +@Component({ + selector: 'app-subscription-list-all', + templateUrl: './subscription-list-all.component.html', + styleUrls: ['./subscription-list-all.component.scss'] +}) +export class SubscriptionListAllComponent implements OnInit { + + + subscriptions: BasicSubscriptionInfo[]; + languages: Language[]; + linksContainer: TermsPrivacyLinksContainer; + queryParams: Params; + + constructor(private subscriptionService: SubscriptionService, + private i18nService: I18nService, + private router: Router, + public translate: TranslateService, + private info: InfoService, + private analytics: AnalyticsService, + private route: ActivatedRoute) { } + + ngOnInit(): void { + zip(this.route.queryParams, this.route.params).pipe( + mergeMap(([params, pathParams]) => { + const searchParams = SearchParams.fromQueryAndPathParams(params, pathParams); + return zip(this.subscriptionService.getSubscriptions(searchParams), this.info.getInfo(), of(searchParams), this.i18nService.getAvailableLanguages()); + }) + ).subscribe(([res, info, searchParams, activeLanguages]) => { + this.queryParams = searchParams.toParams(); + if (res.length === 1) { + this.router.navigate(['/subscription', res[0].id], {replaceUrl: true, queryParams: this.queryParams}); + } else { + this.subscriptions = res; + this.analytics.pageView(info.analyticsConfiguration); + this.linksContainer = globalTermsPrivacyLinks(info); + this.languages = filterAvailableLanguages(activeLanguages, res); + this.i18nService.setPageTitle('subscription.header.title', null); + } + }); + } + +} diff --git a/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.html b/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.html new file mode 100644 index 0000000000..f57bbfea8d --- /dev/null +++ b/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.html @@ -0,0 +1,49 @@ +<dl class="row"> + <ng-container *ngIf="subscription.organizationName"> + <dt class="col-icon text-muted"><fa-icon [icon]="['far', 'building']" a11yRole="presentation"></fa-icon></dt> + <dd class="col-text">{{'show-event.by'|translate}} <strong><a [href]="'mailto:'+ subscription.organizationEmail">{{subscription.organizationName}}</a></strong></dd> + </ng-container> + <ng-container [ngSwitch]="subscription.validityType"> + <ng-container *ngSwitchCase="'NOT_SET'"> + <dt class="col-icon text-muted" *ngIf="subscription.validityType === 'NOT_SET'"><fa-icon [icon]="['fas', 'ticket-alt']" [classes]="['rotate-45']" [title]="'ticket.date-time' | translate"></fa-icon></dt> + <dd class="col-text" *ngIf="subscription.validityType === 'NOT_SET'"> + {{ 'subscription.detail.validity.NOT_SET.description' | translate : { '0': subscription.maxEntries, '1': ('subscription.usage-type.'+subscription.usageType) | translate } }} + </dd> + </ng-container> + <ng-container *ngSwitchCase="'STANDARD'"> + <!-- STANDARD --> + <dt class="col-icon text-muted" *ngIf="subscription.validityType === 'STANDARD'"><fa-icon [icon]="['fas', 'ticket-alt']" [classes]="['rotate-45']" [title]="'ticket.date-time' | translate"></fa-icon></dt> + <dd class="col-text" *ngIf="subscription.validityType === 'STANDARD'"> + {{ 'subscription.detail.validity.STANDARD.description' | translate : { '0': subscription.validityUnits, '1': ('subscription.time-unit.'+subscription.validityTimeUnit | translate), '2': ('subscription.usage-type.'+subscription.usageType) | translate } }} + </dd> + </ng-container> + <ng-container *ngSwitchCase="'CUSTOM'"> + <!-- CUSTOM --> + <dt class="col-icon text-muted" *ngIf="subscription.validityType === 'CUSTOM'"><fa-icon [icon]="['fas', 'ticket-alt']" [classes]="['rotate-45']" [title]="'ticket.date-time' | translate"></fa-icon></dt> + <dd class="col-text" *ngIf="subscription.validityType === 'CUSTOM'"> + <div class="row"> + <div class="col-12"> + <div> + <span class="me-2"><strong translate="subscription.detail.validity.CUSTOM.from"></strong></span> + <span>{{ validFrom }}</span> + <span class="text-muted" *ngIf="displayTimeZoneInfo"> ({{subscription.timeZone}})</span> + </div> + <div *ngIf="hasValidTo"> + <span class="me-2"><strong translate="subscription.detail.validity.CUSTOM.to"></strong></span> + <span>{{ validTo }}</span> + <span class="text-muted" *ngIf="displayTimeZoneInfo"> ({{subscription.timeZone}})</span> + </div> + <div class="mt-1">{{ 'subscription.usage-type.' + subscription.usageType | translate }}</div> + </div> + </div> + </dd> + </ng-container> + </ng-container> + <dt class="col-icon text-muted"><fa-icon [icon]="['fas', 'money-bill']" [title]="'reservation-page.price' | translate"></fa-icon></dt> + <dd class="col-text font-weight-bold"><app-price-tag [formattedPrice]="subscription.formattedPrice" [purchaseContext]="subscription" [showTaxDetails]="true" [singleLineLayout]="true"></app-price-tag></dd> + + <ng-container *ngIf="hasData(owner)"> + <dt class="col-icon text-muted"><fa-icon [icon]="['fas', 'address-card']" [title]="'reservation-page-complete.subscription.owner' | translate"></fa-icon></dt> + <dd class="col-text">{{owner.firstName}} {{owner.lastName}} ({{owner.email}})</dd> + </ng-container> +</dl> diff --git a/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.scss b/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.scss new file mode 100644 index 0000000000..3ce7ee34f6 --- /dev/null +++ b/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.scss @@ -0,0 +1,7 @@ +.col-icon { + width: 40px; +} + +.col-text { + width: calc(100% - 40px); +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.ts b/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.ts new file mode 100644 index 0000000000..b16c7b5b26 --- /dev/null +++ b/frontend/projects/public/src/app/subscription-summary/subscription-summary.component.ts @@ -0,0 +1,67 @@ +import {Component, Input} from '@angular/core'; +import {SubscriptionSummaryData} from '../model/subscription'; +import {getLocalizedContent} from '../shared/subscription.service'; +import {isDifferentTimeZone} from '../shared/event.service'; +import {TranslateService} from '@ngx-translate/core'; +import {SubscriptionOwner} from '../model/reservation-info'; + +@Component({ + selector: 'app-subscription-summary', + templateUrl: './subscription-summary.component.html', + styleUrls: ['./subscription-summary.component.scss'] +}) +export class SubscriptionSummaryComponent { + + @Input() + subscription: SubscriptionSummaryData; + @Input() + owner?: SubscriptionOwner; + + constructor(private translateService: TranslateService) { + } + + get hasOnSaleTo(): boolean { + return this.subscription.formattedOnSaleTo != null; + } + + /* + get onSaleFrom(): string { + return getLocalizedContent(this.subscription.formattedOnSaleFrom, this.translateService.currentLang); + } + + get onSaleTo(): string { + return getLocalizedContent(this.subscription.formattedOnSaleTo, this.translateService.currentLang); + } + */ + + get displayTimeZoneInfo(): boolean { + const datesWithOffset = this.subscription.salePeriod; + return isDifferentTimeZone(datesWithOffset.startDateTime, datesWithOffset.startTimeZoneOffset) + || (datesWithOffset.endDateTime > 0 && isDifferentTimeZone(datesWithOffset.endDateTime, datesWithOffset.endTimeZoneOffset)); + } + + get validFrom(): string { + if (this.subscription.formattedValidFrom != null) { + return getLocalizedContent(this.subscription.formattedValidFrom, this.translateService.currentLang); + } + return ''; + } + + get hasValidTo(): boolean { + return this.subscription.formattedValidTo != null; + } + + get validTo(): string { + if (this.hasValidTo) { + return getLocalizedContent(this.subscription.formattedValidTo, this.translateService.currentLang); + } + return ''; + } + + hasData(owner: SubscriptionOwner): boolean { + return owner != null + && owner.firstName != null + && owner.lastName != null + && owner.email != null; + } +} diff --git a/frontend/projects/public/src/app/ticket-quantity-selector/ticket-quantity-selector.component.ts b/frontend/projects/public/src/app/ticket-quantity-selector/ticket-quantity-selector.component.ts new file mode 100644 index 0000000000..b7d1bd977e --- /dev/null +++ b/frontend/projects/public/src/app/ticket-quantity-selector/ticket-quantity-selector.component.ts @@ -0,0 +1,28 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {TicketCategory} from '../model/ticket-category'; +import {UntypedFormGroup} from '@angular/forms'; + +@Component({ + selector: 'app-ticket-quantity-selector', + templateUrl: './ticket-quantity-selector.html' +}) +export class TicketQuantitySelectorComponent { + + @Input() + parentGroup: UntypedFormGroup; + + @Input() + category: TicketCategory; + + @Input() + quantityRange: number[]; + + @Output() + valueChange = new EventEmitter<number>(); + + formGroup: UntypedFormGroup; + + selectionChanged(): void { + this.valueChange.next(this.parentGroup.get('amount').value); + } +} diff --git a/frontend/projects/public/src/app/ticket-quantity-selector/ticket-quantity-selector.html b/frontend/projects/public/src/app/ticket-quantity-selector/ticket-quantity-selector.html new file mode 100644 index 0000000000..f1f5536ac7 --- /dev/null +++ b/frontend/projects/public/src/app/ticket-quantity-selector/ticket-quantity-selector.html @@ -0,0 +1,10 @@ +<div [formGroup]="parentGroup"> + <ng-container *ngIf="category.saleableAndLimitNotReached"> + <label class="sr-only" [for]="'category-'+category.id+'-qty'" translate="show-event.category.quantity"></label> + <select [id]="'category-'+category.id+'-qty'" formControlName="amount" class="form-select" (change)="selectionChanged()"> + <option *ngFor="let qty of quantityRange" [ngValue]="qty">{{qty}}</option> + </select> + </ng-container> + <span class="font-weight-bold" *ngIf="!category.saleableAndLimitNotReached && category.soldOutOrLimitReached" translate="show-event.sold-out"></span> + <span class="font-weight-bold" *ngIf="!category.saleableAndLimitNotReached && !category.soldOutOrLimitReached" translate="show-event.not-available"></span> +</div> diff --git a/frontend/projects/public/src/app/update-ticket/update-ticket.component.html b/frontend/projects/public/src/app/update-ticket/update-ticket.component.html new file mode 100644 index 0000000000..a5159c1d64 --- /dev/null +++ b/frontend/projects/public/src/app/update-ticket/update-ticket.component.html @@ -0,0 +1,73 @@ +<div *ngIf="event && ticket" class="container mt-2"> + <div class="application-container add-margin-bottom p-md-4"> + + <header> + <app-purchase-context-header [purchaseContext]="event" type="event"></app-purchase-context-header> + </header> + <main class="mt-5"> + <div class="alert mb-5" [ngClass]="{'alert-info': !ticket.onlineEventStarted, 'alert-success': ticket.onlineEventStarted}" *ngIf="isOnlineTicket && !ticketFormVisible"> + <div class="font-weight-bold text-center"> + <span *ngIf="!ticket.onlineEventStarted">{{'event.online.not-started' | translate:{ '0' : ticketOnlineCheckInDate } }}</span> + <span *ngIf="ticket.onlineEventStarted">{{'event.online.started' | translate }}</span> + </div> + </div> + <h2>{{'show-ticket.header.title' | translate:{ '0' : title } }}</h2> + <div class="card mt-5" [ngClass]="{'border-left-warning': !ticket.assigned}"> + <div class="card-body"> + <div class="attendees-data d-md-flex w-100 justify-content-between"> + <h3 class="card-title flex-md-shrink-1"><fa-icon [icon]="['fas', ticket.assigned ? 'check' : 'exclamation-triangle']" size="xs" [classes]="[ ticket.assigned ? 'text-success' : 'text-warning', 'd-none', 'd-md-inline']" a11yRole="presentation"></fa-icon> <small class="ms-2 text-muted">{{categoryName}}</small></h3> + <h3 class="card-title flex-md-shrink-0" *ngIf="ticket.assigned">{{ticket.fullName}}</h3> + <h3 class="card-title text-muted" *ngIf="!ticket.assigned">{{'reservation-page-complete.ticket-not-assigned' | translate }}</h3> + </div> + + <hr *ngIf="!ticketFormVisible"> + + <div class="row justify-content-end" *ngIf="!ticketFormVisible"> + <div class="col-lg-3 col-sm-6 col-12 mb-2" *ngIf="!isOnlineTicket && ticket.assigned && !ticket.cancellationEnabled" > + <a [routerLink]="['/event', event.shortName, 'ticket', ticket.uuid, 'view']" target="_blank" class="btn btn-default block-button"><fa-icon [icon]="['fas', 'search-plus']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.show-ticket'|translate}}</a> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" *ngIf="!isOnlineTicket && ticket.assigned"> + <a [attr.href]="'/api/v2/public/event/' + event.shortName + '/ticket/' + ticket.uuid + '/download-ticket'" class="btn btn-default block-button"><fa-icon [icon]="['fas', 'download']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.download-ticket'|translate}}</a> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" *ngIf="ticket.assigned"> + <button type="button" class="btn btn-default block-button send-ticket-by-email" (click)="sendEmailForTicket()"><fa-icon [icon]="['far', 'envelope']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.send-ticket-by-email-to'|translate}}</button> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" [ngClass]="{'offset-lg-9 offset-sm-6': !ticket.assigned}"> + <button type="button" class="btn btn-default block-button update-ticket-owner" (click)="ticketFormVisible = true"><fa-icon [icon]="ticket.assigned ? ['far', 'edit'] : ['fas', 'check']" a11yRole="presentation"></fa-icon> {{(ticket.assigned ? 'reservation-page-complete.update-ticket-owner' : 'reservation-page-complete.assign') |translate}}</button> + </div> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2" *ngIf="ticket.assigned && ticket.cancellationEnabled"> + <button type="button" class="btn btn-danger block-button" (click)="releaseTicket()"><fa-icon [icon]="['fas', 'eraser']" a11yRole="presentation"></fa-icon> {{'reservation-page-complete.release-button.text'|translate}}</button> + </div> + </div> + + <div class="mt-4" *ngIf="ticketFormVisible"> + <div class="alert alert-info" translate="reservation-page-complete.info-update" *ngIf="event.assignmentConfiguration.enableTicketTransfer && ticket.assigned && !ticket.locked"></div> + <form [formGroup]="formGroup" (submit)="updateTicket()"> + <app-ticket-form [ticket]="ticket" [purchaseContext]="event" [form]="formGroup"></app-ticket-form> + <div class="bg-white pb-3 pt-3" [class.sticky]="ticket.ticketFieldConfigurationAfterStandard.length > 0"> + <div class="row d-flex justify-content-between"> + <div class="col-md-5 order-md-1 col-12"> + <button type="submit" class="btn btn-success block-button" *ngIf="canUpdateTicket">{{ (ticket.assigned ? 'reservation-page-complete.update' : 'reservation-page-complete.assign') | translate}}</button> + </div> + <div class="col-md-5 mt-2 mt-md-0 order-md-0 col-12"> + <button type="button" (click)="ticketFormVisible = false" translate="reservation-page-complete.cancel" class="btn btn-light block-button"></button> + </div> + </div> + </div> + </form> + </div> + + <div class="alert alert-success mt-2 mb-2 hidden alert-dismissible" role="alert" *ngIf="emailSent"> + <button type="button" class="btn-close" (click)="emailSent = false"><span class="sr-only" translate="reservation-page-complete.cancel"></span></button> + <strong translate="email.ticket-email-sent"></strong> + </div> + </div> + </div> + </main> + <footer> + <div class="text-center mt-5"> + <a href="https://alf.io" [attr.title]="'alfio.credits' | translate" target="_blank" rel="noreferrer noopener" translate="alfio.credits"></a> + </div> + </footer> + </div> +</div> diff --git a/frontend/projects/public/src/app/update-ticket/update-ticket.component.ts b/frontend/projects/public/src/app/update-ticket/update-ticket.component.ts new file mode 100644 index 0000000000..ba236eb8d4 --- /dev/null +++ b/frontend/projects/public/src/app/update-ticket/update-ticket.component.ts @@ -0,0 +1,116 @@ +import {Component, OnInit} from '@angular/core'; +import {Event} from '../model/event'; +import {ActivatedRoute, Router} from '@angular/router'; +import {EventService} from '../shared/event.service'; +import {TicketService} from '../shared/ticket.service'; +import {zip} from 'rxjs'; +import {I18nService} from '../shared/i18n.service'; +import {AnalyticsService} from '../shared/analytics.service'; +import {HttpErrorResponse} from '@angular/common/http'; +import {UntypedFormGroup} from '@angular/forms'; +import {Ticket} from '../model/ticket'; +import {handleServerSideValidationError} from '../shared/validation-helper'; +import {TicketsByTicketCategory} from '../model/reservation-info'; +import {TranslateService} from '@ngx-translate/core'; +import {TicketAccessType} from '../model/ticket-category'; + +@Component({ + selector: 'app-update-ticket', + templateUrl: './update-ticket.component.html', + styleUrls: ['./update-ticket.scss'] +}) +export class UpdateTicketComponent implements OnInit { + + event: Event; + ticketIdentifier: string; + ticket: Ticket; + formGroup: UntypedFormGroup; + categoryName: string; + emailSent: boolean; + ticketFormVisible: boolean; + ticketAccessType: TicketAccessType; + + constructor( + private ticketService: TicketService, + private route: ActivatedRoute, + private router: Router, + private eventService: EventService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private translate: TranslateService) { } + + public ngOnInit(): void { + this.route.params.subscribe(params => { + this.ticketIdentifier = params['ticketId']; + + const eventShortName = params['eventShortName']; + + zip(this.eventService.getEvent(eventShortName), this.ticketService.getTicket(eventShortName, this.ticketIdentifier)) + .subscribe(([event, ticketsByCategory]) => { + this.event = event; + this.i18nService.setPageTitle('show-ticket.header.title', event); + this.handleTicketResponse(ticketsByCategory); + this.analytics.pageView(event.analyticsConfiguration); + }, e => { + if (e instanceof HttpErrorResponse && e.status === 404) { + this.router.navigate(['']); + } + }); + }); + } + + private handleTicketResponse(ticketsByCategory: TicketsByTicketCategory): void { + this.ticketAccessType = ticketsByCategory.ticketAccessType; + this.ticket = ticketsByCategory.tickets[0]; + this.formGroup = this.ticketService.buildFormGroupForTicket(this.ticket); + this.categoryName = ticketsByCategory.name; + this.ticketFormVisible = !this.ticket.assigned; + } + + updateTicket(): void { + this.ticketService.updateTicket(this.event.shortName, this.ticket.uuid, this.formGroup.value).subscribe(res => { + if (res.success) { + this.ticketService.getTicket(this.event.shortName, this.ticketIdentifier) + .subscribe(t => this.handleTicketResponse(t)); + } + }, (err) => { + handleServerSideValidationError(err, this.formGroup); + }); + } + + sendEmailForTicket(): void { + this.ticketService.sendTicketByEmail(this.event.shortName, this.ticket.uuid).subscribe(res => { + if (res) { + this.emailSent = true; + } + }); + } + + releaseTicket() { + this.ticketService.openReleaseTicket(this.ticket, this.event.shortName) + .subscribe(released => { + if (released) { + this.router.navigate(['event', this.event.shortName], {replaceUrl: true}); + } + }); + } + + get isOnlineTicket(): boolean { + return this.event.format === 'ONLINE' + || (this.event.format === 'HYBRID' && this.ticketAccessType === 'ONLINE'); + } + + get ticketOnlineCheckInDate(): string { + return this.ticket.formattedOnlineCheckInDate[this.translate.currentLang]; + } + + get canUpdateTicket(): boolean { + return !this.ticket.locked + || this.ticket.ticketFieldConfigurationAfterStandard.length > 0 + || this.ticket.ticketFieldConfigurationBeforeStandard.length > 0; + } + + get title(): string { + return this.event.title[this.translate.currentLang]; + } +} diff --git a/frontend/projects/public/src/app/update-ticket/update-ticket.scss b/frontend/projects/public/src/app/update-ticket/update-ticket.scss new file mode 100644 index 0000000000..d7a58e8a44 --- /dev/null +++ b/frontend/projects/public/src/app/update-ticket/update-ticket.scss @@ -0,0 +1,23 @@ +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} + +.btn-default:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #ccc; +} + +.btn-default:focus, .btn-default.focus { + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); +} + +.sticky { + position: -webkit-sticky; + position: sticky; + bottom: 0px; + left: 0px; + z-index: 200; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/user-logged-in.guard.ts b/frontend/projects/public/src/app/user-logged-in.guard.ts new file mode 100644 index 0000000000..e13c65f30c --- /dev/null +++ b/frontend/projects/public/src/app/user-logged-in.guard.ts @@ -0,0 +1,27 @@ +import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {UserService} from './shared/user.service'; +import {first, map} from 'rxjs/operators'; +import {ANONYMOUS} from './model/user'; + +@Injectable({ + providedIn: 'root' +}) +export class UserLoggedInGuard implements CanActivate { + + constructor(private userService: UserService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> { + return this.userService.authenticationStatus + .pipe( + first(), + map(status => { + if (status.enabled && status.user !== ANONYMOUS) { + return true; + } + return this.router.parseUrl(''); + }) + ); + } +} diff --git a/frontend/projects/public/src/app/view-ticket/view-ticket.component.html b/frontend/projects/public/src/app/view-ticket/view-ticket.component.html new file mode 100644 index 0000000000..fb58186229 --- /dev/null +++ b/frontend/projects/public/src/app/view-ticket/view-ticket.component.html @@ -0,0 +1,73 @@ +<div *ngIf="event && ticketInfo" class="container mt-2"> + <div class="application-container add-margin-bottom p-md-4"> + + <header> + <app-purchase-context-header [purchaseContext]="event" type="event"></app-purchase-context-header> + </header> + <main> + <hr> + <div class="ticket"> + <div class="row"> + <div class="col-md-8 col-12"> + <app-event-summary [event]="event" [dateValidityProvider]="ticketInfo"></app-event-summary> + </div> + <div class="col-md-4 col-12"> + <img src="/api/v2/public/event/{{event.shortName}}/ticket/{{ticketInfo.uuid}}/code.png" + class="img-responsive img-center"> + </div> + </div> + + <div class="mt-3 mb-3"> + <h2 translate="ticket.ticket"></h2> + <dl class="row"> + <dt class="col-sm-3 align-right-mobile-aware" translate="ticket.holder"></dt> + <dd class="col-sm-9">{{ticketInfo.fullName}} <{{ticketInfo.email}}></dd> + <dt class="col-sm-3 align-right-mobile-aware" translate="ticket.type"></dt> + <dd class="col-sm-9">{{ticketInfo.ticketCategoryName}}</dd> + <dt class="col-sm-3 align-right-mobile-aware" translate="ticket.reference-number"></dt> + <dd class="col-sm-9">{{ticketInfo.uuid}}</dd> + <dt class="col-sm-3 align-right-mobile-aware" translate="ticket.order-information"></dt> + <dd class="col-sm-9">{{'ticket.order-information-values' | translate: { + '0': ticketInfo.reservationId, + '1': ticketInfo.reservationFullName + } }}</dd> + </dl> + </div> + + + <div *ngIf="ticketInfo.deskPaymentRequired" class="text-center mt-3 mb-3"> + <h3 translate="ticket.payment-required"></h3> + </div> + + <hr *ngIf="event.format !== 'ONLINE'" /> + + <div class="row mt-5 mb-5" *ngIf="event.format !== 'ONLINE'"> + <div class="col-12 col-md-4 align-self-center mb-5 mb-md-0" *ngIf="gWalletEnabled"> + <a target="_blank" rel="noopener" [attr.href]="'/api/wallet/event/' + event.shortName + '/v1/version/passes/' + ticketInfo.uuid"> + <img src="/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.svg" class="img-responsive mx-auto wallet-link" alt="Add to Google Wallet"> + </a> + </div> + + <div class="col-12 col-md-4 align-self-center mb-5 mb-md-0" *ngIf="passEnabled"> + <a target="_blank" rel="noopener" [attr.href]="'/api/pass/event/' + event.shortName + '/v1/version/passes/' + ticketInfo.uuid"> + <img src="/resources/images/email/add-to-apple-wallet-button.png" class="img-responsive mx-auto wallet-link" alt="Add to Apple Wallet"> + </a> + </div> + + <div [ngClass]="{'offset-md-8': !walletIntegrationEnabled}" class="col-md-4 col-12"> + <a target="_blank" rel="noopener" [attr.href]="'/api/v2/public/event/' + event.shortName + '/ticket/' + ticketInfo.uuid + '/download-ticket'" class="btn btn-link block-button d-flex justify-content-center align-items-center h-100"> + <div class="mr-3"><fa-icon size="2x" [icon]="['fas', 'file-pdf']" a11yRole="presentation"></fa-icon></div> + <div>{{'reservation-page-complete.download-ticket'|translate}} PDF</div> + </a> + </div> + </div> + + </div> + </main> + <footer> + <div class="text-center mt-5"> + <a href="https://alf.io" [attr.title]="'alfio.credits' | translate" target="_blank" rel="noreferrer noopener" translate="alfio.credits"></a> + </div> + </footer> + </div> +</div> diff --git a/frontend/projects/public/src/app/view-ticket/view-ticket.component.scss b/frontend/projects/public/src/app/view-ticket/view-ticket.component.scss new file mode 100644 index 0000000000..69813edd3a --- /dev/null +++ b/frontend/projects/public/src/app/view-ticket/view-ticket.component.scss @@ -0,0 +1,12 @@ +@import "~bootstrap/scss/mixins"; +@import "~bootstrap/scss/functions"; +@import "~bootstrap/scss/variables"; + +img.wallet-link { + @include media-breakpoint-up(md) { + max-height: 56px; + } + @include media-breakpoint-down(md) { + max-width: 250px; + } +} diff --git a/frontend/projects/public/src/app/view-ticket/view-ticket.component.ts b/frontend/projects/public/src/app/view-ticket/view-ticket.component.ts new file mode 100644 index 0000000000..7d1cc25ca5 --- /dev/null +++ b/frontend/projects/public/src/app/view-ticket/view-ticket.component.ts @@ -0,0 +1,68 @@ +import {Component, OnInit} from '@angular/core'; +import {Event} from '../model/event'; +import {ActivatedRoute, Router} from '@angular/router'; +import {EventService} from '../shared/event.service'; +import {TicketService} from '../shared/ticket.service'; +import {TicketInfo} from '../model/ticket-info'; +import {zip} from 'rxjs'; +import {I18nService} from '../shared/i18n.service'; +import {AnalyticsService} from '../shared/analytics.service'; +import {HttpErrorResponse} from '@angular/common/http'; +import {InfoService} from '../shared/info.service'; +import {WalletConfiguration} from '../model/info'; + +@Component({ + selector: 'app-view-ticket', + templateUrl: './view-ticket.component.html', + styleUrls: ['./view-ticket.component.scss'] +}) +export class ViewTicketComponent implements OnInit { + + event: Event; + ticketIdentifier: string; + ticketInfo: TicketInfo; + private walletConfiguration: WalletConfiguration; + + constructor( + private ticketService: TicketService, + private route: ActivatedRoute, + private router: Router, + private eventService: EventService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private infoService: InfoService) { } + + public ngOnInit(): void { + this.route.params.subscribe(params => { + this.ticketIdentifier = params['ticketId']; + + const eventShortName = params['eventShortName']; + + zip(this.eventService.getEvent(eventShortName), this.ticketService.getTicketInfo(eventShortName, this.ticketIdentifier), this.infoService.getInfo()) + .subscribe(([event, ticketInfo, generalInfo]) => { + this.event = event; + this.ticketInfo = ticketInfo; + this.i18nService.setPageTitle('show-ticket.header.title', event); + this.analytics.pageView(event.analyticsConfiguration); + this.walletConfiguration = generalInfo.walletConfiguration; + }, e => { + if (e instanceof HttpErrorResponse && e.status === 404) { + this.router.navigate(['']); + } + }); + }); + } + + get walletIntegrationEnabled(): boolean { + return this.walletConfiguration != null && + (this.walletConfiguration.gWalletEnabled || this.walletConfiguration.passEnabled); + } + + get gWalletEnabled(): boolean { + return this.walletConfiguration != null && this.walletConfiguration.gWalletEnabled; + } + + get passEnabled(): boolean { + return this.walletConfiguration != null && this.walletConfiguration.passEnabled; + } +} diff --git a/frontend/projects/public/src/app/waiting-room/waiting-room.component.html b/frontend/projects/public/src/app/waiting-room/waiting-room.component.html new file mode 100644 index 0000000000..003298114c --- /dev/null +++ b/frontend/projects/public/src/app/waiting-room/waiting-room.component.html @@ -0,0 +1,42 @@ +<div *ngIf="event && ticket" class="container mt-2"> + <div class="application-container add-margin-bottom p-md-4"> + <header> + <app-purchase-context-header [purchaseContext]="event" type="event"></app-purchase-context-header> + </header> + <main class="mt-5"> + <div class="alert d-flex align-items-center justify-content-center" [ngClass]="{'alert-info': eventStartsInTheFuture, 'alert-warning': eventRunning || eventEnded}"> + <div class="me-5"> + <fa-icon size="2x" [icon]="eventStartsInTheFuture ? ['far', 'clock'] : ['fas', 'exclamation-triangle']" a11yRole="presentation"></fa-icon> + </div> + <div> + <h3>{{'common.hello' | translate }} {{ ticket.firstName }}</h3> + <h4> + <ng-container *ngIf="eventStartsInTheFuture">{{'online.check-in.waiting-room.event-not-started' | translate }}</ng-container> + <ng-container *ngIf="eventRunning">{{'online.check-in.waiting-room.event-started' | translate }}</ng-container> + <ng-container *ngIf="eventEnded">{{ 'online.check-in.waiting-room.event-ended' | translate }}</ng-container> + </h4> + <h5 class=""> + <ng-container *ngIf="eventStartsInTheFuture"> + {{'online.check-in.waiting-room.event-not-started.start' | translate }} <strong>{{ checkInDate }} {{ 'common.time.at' | translate }} {{ checkInTime }}</strong> + </ng-container> + <ng-container *ngIf="eventRunning"> + {{ 'online.check-in.waiting-room.event-started.contact-organizers' | translate }} + </ng-container> + </h5> + </div> + </div> + <div class="row justify-content-center mt-5" *ngIf="!eventEnded"> + <div class="col-lg-3 col-sm-6 col-12 mt-2 mt-md-0 mb-2"> + <a [attr.href]="'/event/' + event.shortName + '/ticket/' + ticket.uuid + '/check-in/' + checkInCode" class="btn btn-default block-button"> + <fa-icon [icon]="['fas', 'redo-alt']" a11yRole="presentation"></fa-icon> <span class="ms-2">{{ 'online.check-in.waiting-room.button' | translate }}</span> + </a> + </div> + </div> + </main> + <footer> + <div class="text-center mt-5"> + <a href="https://alf.io" [attr.title]="'alfio.credits' | translate" target="_blank" rel="noreferrer noopener" translate="alfio.credits"></a> + </div> + </footer> + </div> +</div> diff --git a/frontend/projects/public/src/app/waiting-room/waiting-room.component.ts b/frontend/projects/public/src/app/waiting-room/waiting-room.component.ts new file mode 100644 index 0000000000..eeab798865 --- /dev/null +++ b/frontend/projects/public/src/app/waiting-room/waiting-room.component.ts @@ -0,0 +1,82 @@ +import {Component, OnInit} from '@angular/core'; +import {TicketService} from '../shared/ticket.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {EventService} from '../shared/event.service'; +import {I18nService} from '../shared/i18n.service'; +import {AnalyticsService} from '../shared/analytics.service'; +import {zip} from 'rxjs'; +import {HttpErrorResponse} from '@angular/common/http'; +import {mergeMap} from 'rxjs/operators'; +import {Event} from '../model/event'; +import {Ticket} from '../model/ticket'; +import {DateValidity} from '../model/date-validity'; +import {TranslateService} from '@ngx-translate/core'; + +@Component({ + selector: 'app-waiting-room', + templateUrl: './waiting-room.component.html' +}) +export class WaitingRoomComponent implements OnInit { + + event: Event; + ticketIdentifier: string; + ticket: Ticket; + checkInCode: string; + categoryName: string; + checkInInfo: DateValidity; + + constructor( + private ticketService: TicketService, + private route: ActivatedRoute, + private router: Router, + private eventService: EventService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private translate: TranslateService) { } + + ngOnInit(): void { + this.route.params.pipe(mergeMap(params => { + this.ticketIdentifier = params['ticketId']; + const eventShortName = params['eventShortName']; + this.checkInCode = params['ticketCodeHash']; + return zip( + this.eventService.getEvent(eventShortName), + this.ticketService.getTicket(eventShortName, this.ticketIdentifier), + this.ticketService.getOnlineCheckInInfo(eventShortName, this.ticketIdentifier, this.checkInCode) + ); + })).subscribe(([event, ticketInfo, checkInInfo]) => { + this.event = event; + this.ticket = ticketInfo.tickets[0]; + this.categoryName = ticketInfo.name; + this.i18nService.setPageTitle('show-ticket.header.title', event); + this.analytics.pageView(event.analyticsConfiguration); + this.checkInInfo = checkInInfo; + }, e => { + if (e instanceof HttpErrorResponse && e.status === 404) { + this.router.navigate(['']); + } + }); + } + + get checkInDate(): string { + return this.checkInInfo.formattedBeginDate[this.translate.currentLang]; + } + + get checkInTime(): string { + return this.checkInInfo.formattedBeginTime[this.translate.currentLang]; + } + + get eventRunning(): boolean { + const now = new Date().getTime(); + const dates = this.checkInInfo.datesWithOffset; + return now > dates.startDateTime && now < dates.endDateTime; + } + + get eventStartsInTheFuture(): boolean { + return new Date().getTime() < this.checkInInfo.datesWithOffset.startDateTime; + } + + get eventEnded(): boolean { + return new Date().getTime() > this.checkInInfo.datesWithOffset.endDateTime; + } +} diff --git a/frontend/projects/public/src/app/xsrf.ts b/frontend/projects/public/src/app/xsrf.ts new file mode 100644 index 0000000000..957fb52fa5 --- /dev/null +++ b/frontend/projects/public/src/app/xsrf.ts @@ -0,0 +1,110 @@ +import {Inject, Injectable, Injector, INJECTOR, PLATFORM_ID} from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, + HttpXsrfTokenExtractor +} from '@angular/common/http'; +import {DOCUMENT} from '@angular/common'; +import {Observable} from 'rxjs'; +import {tap} from 'rxjs/operators'; + +const GID_HEADER_NAME = 'x-auth-token'; +const XSRF_TOKEN_HEADER = 'xsrf-token'; + +@Injectable() +export class DOMXsrfTokenExtractor implements HttpXsrfTokenExtractor { + + lastToken: string | null = null; + + constructor(@Inject(DOCUMENT) private doc: Document, + @Inject(PLATFORM_ID) private platform: string) {} + + getToken(): string | null { + if (this.platform === 'server') { + return null; + } + return this.doc.head.querySelector<HTMLMetaElement>('meta[name="XSRF_TOKEN"]')?.content || null; + } + + updateToken(newVal: string | null): void { + if (newVal == null || newVal === this.lastToken) { + return; + } + const element = retrieveHeadElement('XSRF_TOKEN', this.doc); + element.content = newVal; + this.lastToken = newVal; + } +} + +@Injectable() +export class DOMGidExtractor { + + lastToken: string | null = null; + + constructor(@Inject(DOCUMENT) private doc: Document, + @Inject(PLATFORM_ID) private platform: string) {} + + getToken(): string | null { + if (this.platform === 'server') { + return null; + } + return this.doc.head.querySelector<HTMLMetaElement>('meta[name="GID"]')?.content || null; + } + + updateToken(newVal: string | null): void { + if (newVal == null || newVal === this.lastToken) { + return; + } + const element = retrieveHeadElement('GID', this.doc); + element.content = newVal; + this.lastToken = newVal; + } +} + +function retrieveHeadElement(name: string, doc: Document): HTMLMetaElement { + let element = doc.head.querySelector<HTMLMetaElement>(`meta[name="${name}"]`); + if (element == null) { + // it should only happen in dev mode + element = doc.createElement('meta'); + element.name = name; + doc.head.append(element); + } + return element; +} + +@Injectable() +export class AuthTokenInterceptor implements HttpInterceptor { + + private domGidExtractor: DOMGidExtractor | null = null; + private domXsrfTokenExtractor: DOMXsrfTokenExtractor | null = null; + + constructor(@Inject(INJECTOR) private injector: Injector) { + } + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + this.init(); + return next.handle(req.clone({ + headers: req.headers.set(GID_HEADER_NAME, this.domGidExtractor?.getToken() || '') + })).pipe(tap(response => { + if (response instanceof HttpResponse) { + if (response.headers.has(GID_HEADER_NAME)) { + this.domGidExtractor?.updateToken(response.headers.get(GID_HEADER_NAME)); + } + if (req.method === 'GET' && response.headers.has(XSRF_TOKEN_HEADER)) { + this.domXsrfTokenExtractor?.updateToken(response.headers.get(XSRF_TOKEN_HEADER)); + } + } + })); + } + + private init(): void { + if (this.domGidExtractor == null) { + this.domGidExtractor = this.injector.get(DOMGidExtractor); + this.domXsrfTokenExtractor = this.injector.get(DOMXsrfTokenExtractor); + } + } + +} diff --git a/frontend/projects/public/src/assets/.gitkeep b/frontend/projects/public/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/projects/public/src/assets/img/payment/mollie-badge.png b/frontend/projects/public/src/assets/img/payment/mollie-badge.png new file mode 100644 index 0000000000..2a462b3d47 Binary files /dev/null and b/frontend/projects/public/src/assets/img/payment/mollie-badge.png differ diff --git a/frontend/projects/public/src/environments/environment.prod.ts b/frontend/projects/public/src/environments/environment.prod.ts new file mode 100644 index 0000000000..3612073bc3 --- /dev/null +++ b/frontend/projects/public/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/frontend/projects/public/src/environments/environment.ts b/frontend/projects/public/src/environments/environment.ts new file mode 100644 index 0000000000..7b4f817adb --- /dev/null +++ b/frontend/projects/public/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/frontend/projects/public/src/index.html b/frontend/projects/public/src/index.html new file mode 100644 index 0000000000..a23c559f3f --- /dev/null +++ b/frontend/projects/public/src/index.html @@ -0,0 +1,15 @@ +<!doctype html> +<html lang="en" class="notranslate" translate="no"> +<head> + <meta charset="utf-8"> + <base href="/"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="google" content="notranslate" /> + <title>Alf.io, the open source ticket reservation system</title> +</head> +<body> + <div> + <app-root></app-root> + </div> +</body> +</html> diff --git a/frontend/projects/public/src/karma.conf.js b/frontend/projects/public/src/karma.conf.js new file mode 100644 index 0000000000..ee9caa152d --- /dev/null +++ b/frontend/projects/public/src/karma.conf.js @@ -0,0 +1,31 @@ +// 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-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; \ No newline at end of file diff --git a/frontend/projects/public/src/main.ts b/frontend/projects/public/src/main.ts new file mode 100644 index 0000000000..484b5f3d3a --- /dev/null +++ b/frontend/projects/public/src/main.ts @@ -0,0 +1,12 @@ +import {enableProdMode} from '@angular/core'; +import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; + +import {AppModule} from './app/app.module'; +import {environment} from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/frontend/projects/public/src/polyfills.ts b/frontend/projects/public/src/polyfills.ts new file mode 100644 index 0000000000..d8a7f59d15 --- /dev/null +++ b/frontend/projects/public/src/polyfills.ts @@ -0,0 +1,60 @@ +/*************************************************************************************************** + * 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 + */ +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. +/** + * 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/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/frontend/projects/public/src/styles.scss b/frontend/projects/public/src/styles.scss new file mode 100644 index 0000000000..1193c9c376 --- /dev/null +++ b/frontend/projects/public/src/styles.scss @@ -0,0 +1,288 @@ +/* You can add global styles to this file, and also import other style files */ + +@import "font/font.scss"; + +$toast-max-width: 20vw; +$card-spacer-y: 1.25rem; +$card-spacer-x: 1.25rem; +@import "bootstrap/scss/functions"; +@import "bootstrap/scss/variables"; +$custom-colors: ( + "default": $white +); + +// Merge the maps +$theme-colors: map-merge($theme-colors, $custom-colors); + +$custom-control-indicator-border-color: $gray-700; + +@import "bootstrap/scss/bootstrap"; + + +@import "@ng-select/ng-select/themes/default.theme.css"; + +.application-container { + background-color: white; +} + +.list-container { + +} + +@include media-breakpoint-up(md) { + body { + background-color: #f2f2f2; + } + .application-container { + margin-top: $border-radius; + margin-bottom: $border-radius; + border-radius: $border-radius; + } +} + + +.block-button { + width: 100%; + display: block +} + +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} + +body { + counter-reset: ticket-counter; +} + +.ticket-counter:before { + counter-increment: ticket-counter; + content: counter(ticket-counter); +} + +.rotate-45 { + transform: rotate(-45deg); +} + +@include media-breakpoint-up(sm) { + label.col-form-label, dt.align-right-mobile-aware { + text-align: right; + } +} + +.list-group-item { + border-left:none; + border-right:none; +} + +.list-group-item:first-child { + border-top:none; +} + +.list-group-item:last-child { + border-bottom:none; +} + +.img-responsive { + display: block; + max-width: 100%; + height: auto; +} + +.img-center { + margin:auto; +} + +.table>thead:first-child>tr:first-child>th { + border-top: 0; +} + +.table th { + font-size: $font-size-base; +} + +.text-align-right { + text-align: right; +} + +.no-margin-bottom { + margin-bottom: 0; +} + +.form-group label { + font-weight: bold; +} + +label.radio-inline { + font-weight: normal; +} + +.checkbox-in-form-group { + margin-top:38px; +} + +@include media-breakpoint-up(sm) { + .container { /*override bootstrap value*/ + max-width: 100%; + } +} + +@include media-breakpoint-up(xl) { + .container { + width: 960px; + } +} + +.invalid-feedback.force-display { + display: block; +} + +.c-container { + text-align: center; +} + +.c-container > div { + display: inline-block; +} + + +.btn-xs { + padding: .30rem .5rem; + font-size: .875rem; + line-height: .5; + border-radius: .2rem; +} + +.add-margin-bottom { + margin-bottom: 1rem; +} + +@media (max-width: 767px) { + .mobile-add-margin-bottom button.btn, + .mobile-add-margin-bottom a.btn + { + margin-bottom: 1rem; + } +} + + +/* basic style for markdown content */ + +.markdown-content img { + max-width:100%; +} + +.markdown-content table { + width:100%; +} + +.markdown-content table th, +.markdown-content table td { + padding-top:0.6rem; + padding-bottom:0.6rem; +} + +.markdown-content table th { + border-bottom: 2px solid #e3e3e3; +} + +.markdown-content table td { + border-top: 1px solid #e3e3e3; +} + +/* end markdown styling */ + +.ng-select.form-control { + padding: 0; +} + +.ng-select.ng-select-focused.form-control { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.ng-select .ng-arrow-wrapper { + + font-weight: $form-select-font-weight; + line-height: $form-select-line-height; + color: $form-select-color; + background-color: $form-select-bg; + background-image: escape-svg($form-select-indicator); + background-repeat: no-repeat; + background-position: $form-select-bg-position; + background-size: $form-select-bg-size; + + .ng-arrow { + border: 0; + } +} + +.ng-select.form-control .ng-select-container { + border: none; + border-radius: 0; + color: #495057; + padding-left: 0!important; + outline: 0 !important; + background: transparent; +} + +.ng-select.form-control.ng-select-focused:not(.ng-select-opened)>.ng-select-container { + outline: 0!important; + box-shadow: none!important; +} + +div.preformatted { + word-wrap: break-word; + white-space: pre-wrap; +} + +h2 .badge { + font-size: 42%; + vertical-align: middle; +} + +h4 .badge { + font-size: 52%; + vertical-align: middle; +} + +div.is-invalid ~ div.invalid-feedback { + display: block; +} + +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} + +:not(.btn-check) + .btn + .btn-default:hover, .btn.btn-default:first-child:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #ccc; +} + +.btn-default:focus, .btn-default.focus { + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); +} + +.border-left-warning { + border-left: 5px solid var(--bs-warning); +} + +.border-left-success { + border-left: 5px solid var(--bs-success); +} + +.home-card { + &:hover { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; + } + h2.card-title { + font-size: 1.6rem; + } +} diff --git a/frontend/projects/public/src/test.ts b/frontend/projects/public/src/test.ts new file mode 100644 index 0000000000..ee6a36318c --- /dev/null +++ b/frontend/projects/public/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/dist/zone-testing'; +import {getTestBed} from '@angular/core/testing'; +import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// 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); diff --git a/frontend/projects/public/tsconfig.app.json b/frontend/projects/public/tsconfig.app.json new file mode 100644 index 0000000000..5714c6c309 --- /dev/null +++ b/frontend/projects/public/tsconfig.app.json @@ -0,0 +1,24 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [], + /* temporarily disable strict checks, see https://www.typescriptlang.org/tsconfig */ + "strict": false, + "noPropertyAccessFromIndexSignature": false + }, + "angularCompilerOptions": { + /* temporarily disable strict checks */ + "strictPropertyInitialization": false, + "strictInjectionParameters": false, + "strictTemplates": false + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/frontend/projects/public/tsconfig.spec.json b/frontend/projects/public/tsconfig.spec.json new file mode 100644 index 0000000000..a9c0752ffe --- /dev/null +++ b/frontend/projects/public/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000000..a6650f1198 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,38 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "paths": { + "common": [ + "dist/common" + ] + }, + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "es2020", + "module": "es2020", + "useDefineForClassFields": false, + "lib": [ + "es2020", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/gradle.properties b/gradle.properties index 3e28826e05..a991bcc2b6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,8 @@ #Tue, 07 Apr 2015 22:45:24 +0200 group=alfio -version=2.0-M4 +version=2.0-M5-SNAPSHOT -sourceCompatibility=11 -targetCompatibility=11 +sourceCompatibility=17 +targetCompatibility=17 systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2" - -# https://jitpack.io/#alfio-event/alf.io-public-frontend -> go to commit tab, set the version -alfioPublicFrontendVersion=908c55db5a \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2f..e708b1c023 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643eed..f42e62f372 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d95..4f906e0c81 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 9618d8d960..107acd32c4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/package-lock.json b/package-lock.json index 706b144637..faab095734 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,1361 @@ { "name": "mjml-to-html", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "mjml-to-html", + "version": "1.0.0", + "dependencies": { + "glob": "^7.1.6", + "mjml": "^4.10.1" + } + }, + "node_modules/@babel/runtime": { + "version": "7.14.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", + "integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "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==", + "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.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", + "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "dependencies": { + "cheerio-select": "^1.5.0", + "dom-serializer": "^1.3.2", + "domhandler": "^4.2.0", + "htmlparser2": "^6.1.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", + "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", + "dependencies": { + "css-select": "^4.1.3", + "css-what": "^5.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0", + "domutils": "^2.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "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==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.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==", + "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==" + }, + "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==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/css-select": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", + "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^5.0.0", + "domhandler": "^4.2.0", + "domutils": "^2.6.0", + "nth-check": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", + "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + }, + "node_modules/dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", + "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dependencies": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "bin": { + "editorconfig": "bin/editorconfig" + } + }, + "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==" + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", + "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "dependencies": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "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==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-beautify": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.0.tgz", + "integrity": "sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ==", + "dependencies": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "nopt": "^5.0.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/juice": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-7.0.0.tgz", + "integrity": "sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==", + "dependencies": { + "cheerio": "^1.0.0-rc.3", + "commander": "^5.1.0", + "mensch": "^0.3.4", + "slick": "^1.12.2", + "web-resource-inliner": "^5.0.0" + }, + "bin": { + "juice": "bin/juice" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/juice/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/mensch": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", + "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==" + }, + "node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mjml": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.10.1.tgz", + "integrity": "sha512-cD6F20yIMOUT9xaAVWLfk0DnhQSq6VkpD+LCf/RUWrxsxxkwPJtJfKfrP5TMeuWcmDFS9hfOBS3Jz+NmGh03jQ==", + "dependencies": { + "@babel/runtime": "^7.14.6", + "mjml-cli": "4.10.1", + "mjml-core": "4.10.1", + "mjml-migrate": "4.10.1", + "mjml-preset-core": "4.10.1", + "mjml-validator": "4.10.1" + }, + "bin": { + "mjml": "bin/mjml" + } + }, + "node_modules/mjml-accordion": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.10.1.tgz", + "integrity": "sha512-wQ2PHsiuq3wxsumb78T7gWimB9aQXWP1yDQrQiLn+hmARiLOv9KOn96ZffM4nWyNlDM0AsWWm9fpLgv1RwI8wQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-body": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.10.1.tgz", + "integrity": "sha512-6+v/m+PhTkm8NZYim1g2H0GsQ9sydZQZiaGCnzSpKJL6HXMYHqckdUc/yvgph86/XPRDTv2dIv/tvgJ5dBvFlA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-button": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.10.1.tgz", + "integrity": "sha512-3lmez3NZt1TJjE+HLZO10q9dfminH5JtTEV2m9Yf/gXRoT+vpD7XFs5vrAXVhMh879Unc3pxY8ghFr0/8JLBgQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-carousel": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.10.1.tgz", + "integrity": "sha512-5n9LKP0KANRCoZDgT7jJCMiKlWr26idy1Sc2PunJ71164/lE6HKhSvMISNEdxDm+ZTj4Y2vDeobcc3zviQRkcw==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-cli": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.10.1.tgz", + "integrity": "sha512-M/N704fblgiJOqyqD6PoEi0F4ILnb5PLFsRF0BE8NNoCpQz0WnnnUjuCnPBfgCsz/rVxBrSXYpGNrpViCkgftQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "chokidar": "^3.0.0", + "glob": "^7.1.1", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.15", + "mjml-core": "4.10.1", + "mjml-migrate": "4.10.1", + "mjml-parser-xml": "4.10.1", + "mjml-validator": "4.10.1", + "yargs": "^16.1.0" + }, + "bin": { + "mjml-cli": "bin/mjml" + } + }, + "node_modules/mjml-column": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.10.1.tgz", + "integrity": "sha512-ajn2YHc3rWXhXYsJiOvYnHBWbSB+ep1dTs+qe2uhxq9h8BdvG9bxh5UZeN3do7IEwqZIeA7lFKeWZyu07LapCQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-core": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.10.1.tgz", + "integrity": "sha512-2RsFeWteelaaijD6dDFsvpgc/Zt5caKmUCr1sI3u7gIpH17LEjS5+CcSTEUwAOyAztUxpNfdotuWs3W0i2YyCA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "cheerio": "1.0.0-rc.10", + "detect-node": "2.0.4", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "juice": "^7.0.0", + "lodash": "^4.17.15", + "mjml-migrate": "4.10.1", + "mjml-parser-xml": "4.10.1", + "mjml-validator": "4.10.1" + } + }, + "node_modules/mjml-divider": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.10.1.tgz", + "integrity": "sha512-k3Rs+Owa9zdS49LbSGA+KkI9XFnAjJpINCYSfwp0p05HP+80jZAVIyb8/oMVjwzcM3CCPQhqSxg6WSuLMDfezg==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-group": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.10.1.tgz", + "integrity": "sha512-uW5lIA3vRYqyUU7rzv2rMcUZkPkol4l4GJPnDvx5Pm45H9ogD8GgdhlPUq95cKRGEOqxj9ytFXqKrb1SlfDi3g==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.10.1.tgz", + "integrity": "sha512-NYnITF3yZvN4pemiJISW7rINjaQtVmtFwNROleHRBNj/7GleklSo6cMOYljhJ95qzUyFlEVPbO54+znt1IlQHw==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-attributes": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.10.1.tgz", + "integrity": "sha512-o6Peu/NcoQ45YTIfuVrE5QDutl8Zi+Zp7owkF6yDFPqQfVR8QCTQ/JS+3Z0bI/HRXoljV6O7eCBA6HbDdvMiaQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-breakpoint": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.10.1.tgz", + "integrity": "sha512-2QJxmrQqB0N+Crfn+Cguq5eeL8YQ3y4g2K3UQ1d7dB0rEiwWN5x9gb4iOrrPTagepdYiaELJo9RqwR26WGw+9A==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-font": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.10.1.tgz", + "integrity": "sha512-WjEyBneeyRaeEnW/iqfOMLGvkpSSijqHQxk/z6yVVQ+fEcZ6kOZFPIVDrXSgLUpPso0GV46ngde57zWuBYuTUQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-html-attributes": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.10.1.tgz", + "integrity": "sha512-ZT5DPfCauY0N6JR4ouJaJ3dYiUDbsXWE27A5bGTUNF19fl59571z/lfTgCUnW4veXmG4n4JvOQYdw8QLotn7/w==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-preview": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.10.1.tgz", + "integrity": "sha512-aeWHNiIAiREraDWwdniqWt3Vy97Ao/hxu46FJFwLn7q4Za3ZevxeHzzIoPgkGB8qaOSKHeyYDLHwhzLqnQAdKw==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-style": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.10.1.tgz", + "integrity": "sha512-iCY5JsxJU9wiqh4A20CxeAv2MLqUBKotDS6V4lD16QVR2U1GDc7ZfP7rU8bL8ULE/589RxmkMq/+w3W5Fyx56g==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-head-title": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.10.1.tgz", + "integrity": "sha512-rw18SV3X0mGi+59cZUFxrsXKFhK0sO1NniwKyxAtb/9cjlHiMbiXYeSOaw8Yg4Ee++C37xOd5hWCCxXUj09kFw==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-hero": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.10.1.tgz", + "integrity": "sha512-3MozfbGnF7XbVzBJ1iz6F7nTDnJUZTRgaIbPhFQXDCbJ33LN6IPPS1+22iQnlZQeTG9N4SpJLjlG2W8UQp7rew==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-image": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.10.1.tgz", + "integrity": "sha512-LZr49qbXDa/i9wM3iBOdwF9zndYn/xTHaofUn3rUxVmybfGLmr/N5fc84ZGMs4kBWYz8kuHodjOnaY/9g58pyg==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-migrate": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.10.1.tgz", + "integrity": "sha512-RfUUqW9oqxp/42EVVygpu7v1/TX0TksJS3+7cAAuMSh8WsXQGYaNDDpFjItQapMDYfOVT/0Uby1L8PiuhQtx+g==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "js-beautify": "^1.6.14", + "lodash": "^4.17.15", + "mjml-core": "4.10.1", + "mjml-parser-xml": "4.10.1", + "yargs": "^16.1.0" + }, + "bin": { + "migrate": "lib/cli.js" + } + }, + "node_modules/mjml-navbar": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.10.1.tgz", + "integrity": "sha512-Fu6/1WxXFgY478OINmiCrB3tE2c2uaEpXNS0ac2EbIytaM7/azQ58+Y07jfie1UXM6cjHAb5ZWOTjvJoPPOdag==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-parser-xml": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.10.1.tgz", + "integrity": "sha512-dQW7efbPTXY9cgEshxefiEQ6lcWXp1yADjfv6hWYhcWlpeBfyZ6NBDL8aoRoem+5+avMSzn1tP1oRvedlSY/9Q==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "detect-node": "2.0.4", + "htmlparser2": "^4.1.0", + "lodash": "^4.17.15" + } + }, + "node_modules/mjml-parser-xml/node_modules/domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "dependencies": { + "domelementtype": "^2.0.1" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/mjml-parser-xml/node_modules/htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "node_modules/mjml-preset-core": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.10.1.tgz", + "integrity": "sha512-zAD72N3xb5vxOedd446g1lAvMeh57JZ5HDUUFNB473QVvu7HgX2OShykwsgyzgYgKXE3MBhms8nr1jv55SnPuA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "mjml-accordion": "4.10.1", + "mjml-body": "4.10.1", + "mjml-button": "4.10.1", + "mjml-carousel": "4.10.1", + "mjml-column": "4.10.1", + "mjml-divider": "4.10.1", + "mjml-group": "4.10.1", + "mjml-head": "4.10.1", + "mjml-head-attributes": "4.10.1", + "mjml-head-breakpoint": "4.10.1", + "mjml-head-font": "4.10.1", + "mjml-head-html-attributes": "4.10.1", + "mjml-head-preview": "4.10.1", + "mjml-head-style": "4.10.1", + "mjml-head-title": "4.10.1", + "mjml-hero": "4.10.1", + "mjml-image": "4.10.1", + "mjml-navbar": "4.10.1", + "mjml-raw": "4.10.1", + "mjml-section": "4.10.1", + "mjml-social": "4.10.1", + "mjml-spacer": "4.10.1", + "mjml-table": "4.10.1", + "mjml-text": "4.10.1", + "mjml-wrapper": "4.10.1" + } + }, + "node_modules/mjml-raw": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.10.1.tgz", + "integrity": "sha512-CgVn5E9c4uEvn10JMoOLrbcKLpaN1JAyl0d1M01v2kp1iG6VnfMv0BydliC7nZ4kHk7+bJ51XjkMBl9k+KDZYA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-section": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.10.1.tgz", + "integrity": "sha512-9QC5tyJ4hgbA/lRLZRNgIh866/VTfLc7+ucKkaWnteeF/CCxjqTwcYrLPbR5HKEMSjVd5/T17SOVOB6jv/VIdg==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-social": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.10.1.tgz", + "integrity": "sha512-YuPjJF60oXQ47cd0gtJfy8SZBBBM8HV7pFwIuD1NIKdTU6qPZj38X81ZKPPuvmD1td+FVG/W/mySzuuH1hZkWQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-spacer": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.10.1.tgz", + "integrity": "sha512-/wgxbrAIGrjpB5Z3KZUKDliQ/bdUsEouXgbMjRqoPfgskVV6ocrSLMDjWT8XEpYPSKb5T4p3dlWn8XKKl1nrOw==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-table": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.10.1.tgz", + "integrity": "sha512-bfztBF/leXqbVHcOUiQu4zaSlW7i7lE2q0mb8gPHPRbh9cPzwetk5pYh9Gc7PiMpodvzpAYR2OeaQS0Z4FfjKg==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-text": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.10.1.tgz", + "integrity": "sha512-SjB6U3O6TjkaeonQ85+BKfIQUGMf4gybS1QuI2ULlAMPlNYpmkNBhPzlXNECWUG1OETg1HgD6WqEYBG8Dd970A==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1" + } + }, + "node_modules/mjml-validator": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.10.1.tgz", + "integrity": "sha512-tYnRLtupjAj82Do9gb2Y8hWGSGz6nTLV6csia11JubhLoykJNS3JnGW2CPZ/Y+zWNUxJMNHkgHnxAPwSlmX6dQ==", + "dependencies": { + "@babel/runtime": "^7.8.7" + } + }, + "node_modules/mjml-wrapper": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.10.1.tgz", + "integrity": "sha512-z0ewwxWPZAnIMKqsRCHh/jjsgPNcMO4Yej32LLait6IOAiUmo2hWLhrDmSFY69NrzuxxQPBI7tZ8G5LUbaH/EQ==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "lodash": "^4.17.15", + "mjml-core": "4.10.1", + "mjml-section": "4.10.1" + } + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", + "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, + "node_modules/slick": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", + "integrity": "sha1-vQSN23TefRymkV+qSldXCzVQwtc=", + "engines": { + "node": "*" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/uglify-js": { + "version": "3.13.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", + "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" + }, + "node_modules/valid-data-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", + "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/web-resource-inliner": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-5.0.0.tgz", + "integrity": "sha512-AIihwH+ZmdHfkJm7BjSXiEClVt4zUFqX4YlFAzjL13wLtDuUneSaFvDBTbdYRecs35SiU7iNKbMnN+++wVfb6A==", + "dependencies": { + "ansi-colors": "^4.1.1", + "escape-goat": "^3.0.0", + "htmlparser2": "^4.0.0", + "mime": "^2.4.6", + "node-fetch": "^2.6.0", + "valid-data-url": "^3.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/web-resource-inliner/node_modules/domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "dependencies": { + "domelementtype": "^2.0.1" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "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==", + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "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/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==", + "engines": { + "node": ">=10" + } + } + }, "dependencies": { "@babel/runtime": { "version": "7.14.6", diff --git a/src/main/dist/Dockerfile b/src/main/dist/Dockerfile index f8713c944f..31b995f6e2 100644 --- a/src/main/dist/Dockerfile +++ b/src/main/dist/Dockerfile @@ -5,18 +5,18 @@ # - https://august.nagro.us/small-java.html # -FROM azul/zulu-openjdk-alpine:15.0.2 as zulu +FROM azul/zulu-openjdk-alpine:17 as zulu RUN $JAVA_HOME/bin/jlink --compress=1 --strip-java-debug-attributes --no-header-files --no-man-pages \ --module-path $JAVA_HOME/jmods \ - # see https://github.com/ben-manes/caffeine/issues/273 + # see https://github.com/ben-manes/caffeine/issues/273 TODO investigate if we can remove jdk.unsupported # see https://docs.oracle.com/en/java/javase/11/security/oracle-providers.html#GUID-9224B90B-7B2F-41F9-BB96-C0A1B6A0FEAA --add-modules java.desktop,java.logging,java.sql,java.management,java.naming,jdk.unsupported,jdk.crypto.ec,java.net.http,jdk.localedata \ --include-locales en,it,es,nl,fr,de,ro,pt,tr,pl,da,bg,sv \ --output /jlinked -FROM alpine:3.12 -LABEL org.opencontainers.image.source https://github.com/alfio-event/alf.io +FROM alpine:3.15 +LABEL org.opencontainers.image.source=https://github.com/alfio-event/alf.io COPY --from=zulu /jlinked /opt/jdk/ diff --git a/src/main/java/alfio/config/BaseConfiguration.java b/src/main/java/alfio/config/BaseConfiguration.java index 9cf3122667..1dffe3ea5f 100644 --- a/src/main/java/alfio/config/BaseConfiguration.java +++ b/src/main/java/alfio/config/BaseConfiguration.java @@ -56,12 +56,16 @@ ConfigurationManager configurationManager(ConfigurationRepository configurationR cache); } - @Bean - ObjectMapper objectMapper() { + public static ObjectMapper buildObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); return mapper; } + + @Bean + ObjectMapper objectMapper() { + return buildObjectMapper(); + } } diff --git a/src/main/java/alfio/config/ConfigurationStatusChecker.java b/src/main/java/alfio/config/ConfigurationStatusChecker.java index 77521b450b..7173da429b 100644 --- a/src/main/java/alfio/config/ConfigurationStatusChecker.java +++ b/src/main/java/alfio/config/ConfigurationStatusChecker.java @@ -25,7 +25,8 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.UserRepository; import alfio.util.PasswordGenerator; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationListener; @@ -37,9 +38,10 @@ import static java.util.Optional.ofNullable; @Component -@Log4j2 public class ConfigurationStatusChecker implements ApplicationListener<ContextRefreshedEvent> { + private static final Logger log = LoggerFactory.getLogger(ConfigurationStatusChecker.class); + private final ConfigurationManager configurationManager; private final UserRepository userRepository; private final AuthorityRepository authorityRepository; diff --git a/src/main/java/alfio/config/DataSourceConfiguration.java b/src/main/java/alfio/config/DataSourceConfiguration.java index 7b722c88ad..a6d798bbcb 100644 --- a/src/main/java/alfio/config/DataSourceConfiguration.java +++ b/src/main/java/alfio/config/DataSourceConfiguration.java @@ -22,18 +22,12 @@ import alfio.config.support.PlatformProvider; import alfio.extension.ExtensionService; import alfio.job.Jobs; -import alfio.job.executor.AssignTicketToSubscriberJobExecutor; -import alfio.job.executor.BillingDocumentJobExecutor; -import alfio.job.executor.ReservationJobExecutor; -import alfio.job.executor.RetryFailedExtensionJobExecutor; +import alfio.job.executor.*; import alfio.manager.*; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.system.AdminJobManager; import alfio.manager.system.ConfigurationManager; -import alfio.repository.EventDeleterRepository; -import alfio.repository.EventRepository; -import alfio.repository.SubscriptionRepository; -import alfio.repository.TicketCategoryRepository; +import alfio.repository.*; import alfio.repository.system.AdminJobQueueRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; @@ -47,10 +41,11 @@ import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; import ch.digitalfondue.npjt.mapper.ParameterConverter; import com.zaxxer.hikari.HikariDataSource; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.*; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; @@ -84,10 +79,11 @@ @EnableScheduling @EnableAsync @ComponentScan(basePackages = {"alfio.manager", "alfio.extension"}) -@Log4j2 @EnableNpjt(basePackages = "alfio.repository") public class DataSourceConfiguration { + private static final Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class); + private static final Set<PlatformProvider> PLATFORM_PROVIDERS = EnumSet.complementOf(EnumSet.of(PlatformProvider.DEFAULT)); @Bean @@ -255,9 +251,10 @@ AdminJobManager adminJobManager(AdminJobQueueRepository adminJobQueueRepository, ReservationJobExecutor reservationJobExecutor, BillingDocumentJobExecutor billingDocumentJobExecutor, AssignTicketToSubscriberJobExecutor assignTicketToSubscriberJobExecutor, - RetryFailedExtensionJobExecutor retryFailedExtensionJobExecutor) { + RetryFailedExtensionJobExecutor retryFailedExtensionJobExecutor, + RetryFailedReservationConfirmationExecutor retryFailedReservationConfirmationExecutor) { return new AdminJobManager( - List.of(reservationJobExecutor, billingDocumentJobExecutor, assignTicketToSubscriberJobExecutor, retryFailedExtensionJobExecutor), + List.of(reservationJobExecutor, billingDocumentJobExecutor, assignTicketToSubscriberJobExecutor, retryFailedExtensionJobExecutor, retryFailedReservationConfirmationExecutor), adminJobQueueRepository, transactionManager, clockProvider); @@ -297,16 +294,21 @@ RetryFailedExtensionJobExecutor retryFailedExtensionJobExecutor(ExtensionService return new RetryFailedExtensionJobExecutor(extensionService); } + @Bean + RetryFailedReservationConfirmationExecutor retryFailedReservationConfirmationExecutor(ReservationFinalizer reservationFinalizer, Json json) { + return new RetryFailedReservationConfirmationExecutor(reservationFinalizer, json); + } + @Bean @Profile(Initializer.PROFILE_DEMO) DemoModeDataManager demoModeDataManager(UserRepository userRepository, UserOrganizationRepository userOrganizationRepository, - OrganizationRepository organizationRepository, EventDeleterRepository eventDeleterRepository, EventRepository eventRepository, - ConfigurationManager configurationManager) { - return new DemoModeDataManager(userRepository, userOrganizationRepository, organizationRepository, - eventDeleterRepository, eventRepository, configurationManager); + ConfigurationManager configurationManager, + OrganizationDeleterRepository organizationDeleterRepository) { + return new DemoModeDataManager(userRepository, userOrganizationRepository, + eventDeleterRepository, eventRepository, configurationManager, organizationDeleterRepository); } /** diff --git a/src/main/java/alfio/config/Initializer.java b/src/main/java/alfio/config/Initializer.java index 824ca60ff0..6cc0892b4b 100644 --- a/src/main/java/alfio/config/Initializer.java +++ b/src/main/java/alfio/config/Initializer.java @@ -42,6 +42,8 @@ public class Initializer extends AbstractAnnotationConfigDispatcherServletInitia public static final String PROFILE_DEMO = "demo"; public static final String PROFILE_OPENID = "openid"; public static final String PROFILE_DISABLE_JOBS = "disable-jobs"; + public static final String API_V2_PUBLIC_PATH = "/api/v2/public/"; + public static final String XSRF_TOKEN = "XSRF-TOKEN"; private Environment environment; @Override diff --git a/src/main/java/alfio/config/MvcConfiguration.java b/src/main/java/alfio/config/MvcConfiguration.java index 1e089e91e5..4ca3be85cc 100644 --- a/src/main/java/alfio/config/MvcConfiguration.java +++ b/src/main/java/alfio/config/MvcConfiguration.java @@ -28,7 +28,12 @@ import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; +import org.springframework.session.web.http.CookieHttpSessionIdResolver; +import org.springframework.session.web.http.HeaderHttpSessionIdResolver; +import org.springframework.session.web.http.HttpSessionIdResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -37,27 +42,29 @@ import org.springframework.web.servlet.view.AbstractUrlBasedView; import org.springframework.web.servlet.view.UrlBasedViewResolver; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.time.Duration; import java.util.Collections; import java.util.List; +import static alfio.config.Initializer.API_V2_PUBLIC_PATH; + + @Configuration(proxyBeanMethods = false) @ComponentScan(basePackages = {"alfio.controller", "alfio.config"}) @EnableWebMvc -@EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 4 * 60 * 60) //4h +@EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 4 * 60 * 60, tableName = "ALFIO_SPRING_SESSION") //4h public class MvcConfiguration implements WebMvcConfigurer { private final Environment environment; - private final String frontendVersion; private final String alfioVersion; private final ObjectMapper objectMapper; public MvcConfiguration(Environment environment, - @Value("${alfio.frontend.version}") String frontendVersion, @Value("${alfio.version}") String alfioVersion, ObjectMapper objectMapper) { this.environment = environment; - this.frontendVersion = frontendVersion; this.alfioVersion = alfioVersion; this.objectMapper = objectMapper; } @@ -83,21 +90,16 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { .addResourceLocations("/resources/images/") .setCachePeriod(cacheMinutes * 60) .setCacheControl(defaultCacheControl); - // - registry - .addResourceHandler("/webjars/**") - .addResourceLocations("/webjars/") - .setCacheControl(defaultCacheControl); - registry.addResourceHandler("/assets/**") - .addResourceLocations("/webjars/alfio-public-frontend/" + frontendVersion + "/alfio-public-frontend/assets/") + registry.addResourceHandler("/frontend-public/**") + .addResourceLocations("/resources/alfio-public-frontend/") .setCachePeriod(cacheMinutes * 60) - .setCacheControl(defaultCacheControl); + .setCacheControl(CacheControl.maxAge(Duration.ofDays(60))); - registry.addResourceHandler("/*.js") - .addResourceLocations("/webjars/alfio-public-frontend/" + frontendVersion + "/alfio-public-frontend/") + registry.addResourceHandler("/frontend-admin/**") + .addResourceLocations("/resources/alfio-admin-frontend/") .setCachePeriod(cacheMinutes * 60) - .setCacheControl(defaultCacheControl); + .setCacheControl(CacheControl.maxAge(Duration.ofDays(60))); } @@ -128,4 +130,45 @@ public ViewResolver viewResolver() { resolver.setViewClass(AbstractUrlBasedView.class); return resolver; } + + @Bean + public SpringSessionBackedSessionRegistry<?> sessionRegistry(FindByIndexNameSessionRepository<?> sessionRepository) { + return new SpringSessionBackedSessionRegistry<>(sessionRepository); + } + + @Bean + public HttpSessionIdResolver httpSessionIdResolver() { + var publicSessionIdResolver = HeaderHttpSessionIdResolver.xAuthToken(); + var adminSessionIdResolver = new CookieHttpSessionIdResolver(); + return new HttpSessionIdResolver() { + @Override + public List<String> resolveSessionIds(HttpServletRequest request) { + return isPublic(request) ? publicSessionIdResolver.resolveSessionIds(request) + : adminSessionIdResolver.resolveSessionIds(request); + } + + @Override + public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) { + if (isPublic(request)) { + publicSessionIdResolver.setSessionId(request, response, sessionId); + } else { + adminSessionIdResolver.setSessionId(request, response, sessionId); + } + } + + @Override + public void expireSession(HttpServletRequest request, HttpServletResponse response) { + if (isPublic(request)) { + publicSessionIdResolver.expireSession(request, response); + } else { + adminSessionIdResolver.expireSession(request, response); + } + } + + private static boolean isPublic(HttpServletRequest request) { + var requestURI = request.getRequestURI(); + return requestURI.startsWith(API_V2_PUBLIC_PATH); + } + }; + } } diff --git a/src/main/java/alfio/config/RoleAndOrganizationsTransactionPreparer.java b/src/main/java/alfio/config/RoleAndOrganizationsTransactionPreparer.java index c5dd5180bf..f553abe9cc 100644 --- a/src/main/java/alfio/config/RoleAndOrganizationsTransactionPreparer.java +++ b/src/main/java/alfio/config/RoleAndOrganizationsTransactionPreparer.java @@ -17,7 +17,8 @@ package alfio.config; import alfio.config.authentication.support.OpenIdAlfioAuthentication; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -34,9 +35,15 @@ import java.util.TreeSet; import java.util.stream.Collectors; -@Log4j2 +import static alfio.config.authentication.support.AuthenticationConstants.ADMIN; +import static alfio.config.authentication.support.AuthenticationConstants.SYSTEM_API_CLIENT; + class RoleAndOrganizationsTransactionPreparer { + private static final Logger log = LoggerFactory.getLogger(RoleAndOrganizationsTransactionPreparer.class); + + private RoleAndOrganizationsTransactionPreparer() {} + private static final OrRequestMatcher IS_PUBLIC_URLS = new OrRequestMatcher( new AntPathRequestMatcher("/resources/**"), new AntPathRequestMatcher("/webjars/**"), @@ -82,7 +89,7 @@ private static boolean isAdmin() { return SecurityContextHolder.getContext().getAuthentication() .getAuthorities().stream() .map(GrantedAuthority::getAuthority) - .anyMatch("ROLE_ADMIN"::equals); + .anyMatch(authority -> authority.equals("ROLE_" + SYSTEM_API_CLIENT) || authority.equals("ROLE_" + ADMIN)); } return false; } diff --git a/src/main/java/alfio/config/SpringBootInitializer.java b/src/main/java/alfio/config/SpringBootInitializer.java index 0654a9f16c..3ed7dd51ce 100644 --- a/src/main/java/alfio/config/SpringBootInitializer.java +++ b/src/main/java/alfio/config/SpringBootInitializer.java @@ -21,49 +21,33 @@ import com.openhtmltopdf.util.XRLog; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Profiles; -import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import javax.servlet.Filter; -import javax.servlet.SessionCookieConfig; import java.time.Clock; import java.util.logging.Level; -import static org.springframework.web.context.support.WebApplicationContextUtils.getRequiredWebApplicationContext; - @EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration.class, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.class, org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.class, org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration.class, org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.class, org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration.class, - org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration.class}) + org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration.class, + org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class +}) @EnableConfigurationProperties(ExternalConfiguration.class) @Configuration(proxyBeanMethods = false) @Profile(Initializer.PROFILE_SPRING_BOOT) public class SpringBootInitializer { - @Bean - public ServletContextInitializer servletContextInitializer() { - return servletContext -> { - WebApplicationContext ctx = getRequiredWebApplicationContext(servletContext); - ConfigurableEnvironment environment = ctx.getBean(ConfigurableEnvironment.class); - SessionCookieConfig config = servletContext.getSessionCookieConfig(); - config.setHttpOnly(true); - config.setSecure(environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_LIVE))); - // force log initialization, then disable it - XRLog.setLevel(XRLog.EXCEPTION, Level.WARNING); - XRLog.setLoggingEnabled(false); - }; - } - @Bean public Filter characterEncodingFilter() { CharacterEncodingFilter cef = new CharacterEncodingFilter(); @@ -76,4 +60,16 @@ public Filter characterEncodingFilter() { public ClockProvider clockProvider() { return ClockProvider.init(Clock.systemUTC()); } + + @Bean + public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> applicationCookieSameSiteSupplier() { + return factory -> { + factory.addCookieSameSiteSuppliers(CookieSameSiteSupplier.ofStrict()); + factory.addInitializers(servletContext -> { + // force log initialization, then disable it + XRLog.setLevel(XRLog.EXCEPTION, Level.WARNING); + XRLog.setLoggingEnabled(false); + }); + }; + } } diff --git a/src/main/java/alfio/config/SpringBootLauncher.java b/src/main/java/alfio/config/SpringBootLauncher.java index 5fc52602c1..fdc9d9de60 100644 --- a/src/main/java/alfio/config/SpringBootLauncher.java +++ b/src/main/java/alfio/config/SpringBootLauncher.java @@ -17,7 +17,8 @@ package alfio.config; import alfio.util.DefaultExceptionHandler; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; @@ -25,9 +26,10 @@ import java.util.ArrayList; import java.util.List; -@Log4j2 public class SpringBootLauncher { + private static final Logger log = LoggerFactory.getLogger(SpringBootLauncher.class); + /** * Entry point for spring boot * @param args original arguments diff --git a/src/main/java/alfio/config/WebSecurityConfig.java b/src/main/java/alfio/config/WebSecurityConfig.java index 992f91cc77..51ab22515c 100644 --- a/src/main/java/alfio/config/WebSecurityConfig.java +++ b/src/main/java/alfio/config/WebSecurityConfig.java @@ -26,7 +26,6 @@ import alfio.repository.user.UserRepository; import alfio.repository.user.join.UserOrganizationRepository; import alfio.util.Json; -import lombok.extern.log4j.Log4j2; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -39,9 +38,10 @@ import java.net.http.HttpClient; +import static alfio.config.Initializer.PROFILE_OPENID; + @Configuration(proxyBeanMethods = false) @EnableWebSecurity -@Log4j2 public class WebSecurityConfig { public static final String CSRF_PARAM_NAME = "_csrf"; @@ -56,7 +56,7 @@ public CsrfTokenRepository getCsrfTokenRepository() { } @Bean - @Profile("openid") + @Profile(PROFILE_OPENID) public AdminOpenIdAuthenticationManager adminOpenIdAuthenticationManager(Environment environment, HttpClient httpClient, ConfigurationManager configurationManager, diff --git a/src/main/java/alfio/config/authentication/APITokenAuthWebSecurity.java b/src/main/java/alfio/config/authentication/APITokenAuthWebSecurity.java index 8d61f7df4c..669f84df41 100644 --- a/src/main/java/alfio/config/authentication/APITokenAuthWebSecurity.java +++ b/src/main/java/alfio/config/authentication/APITokenAuthWebSecurity.java @@ -16,81 +16,111 @@ */ package alfio.config.authentication; -import alfio.config.authentication.support.APIKeyAuthFilter; -import alfio.config.authentication.support.APITokenAuthentication; -import alfio.config.authentication.support.RequestTypeMatchers; -import alfio.config.authentication.support.WrongAccountTypeException; +import alfio.config.authentication.support.*; +import alfio.model.system.ConfigurationKeys; import alfio.model.user.User; +import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.AuthorityRepository; import alfio.repository.user.UserRepository; import alfio.util.ClockProvider; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.web.SecurityFilterChain; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.time.ZonedDateTime; -import java.util.stream.Collectors; +import java.util.List; -import static alfio.config.authentication.AuthenticationConstants.*; +import static alfio.config.authentication.support.AuthenticationConstants.*; @Configuration(proxyBeanMethods = false) @Order(0) -public class APITokenAuthWebSecurity extends WebSecurityConfigurerAdapter { +public class APITokenAuthWebSecurity { + public static final String API_KEY = "Api key "; private final UserRepository userRepository; private final AuthorityRepository authorityRepository; + private final ConfigurationRepository configurationRepository; public APITokenAuthWebSecurity(UserRepository userRepository, - AuthorityRepository authorityRepository) { + AuthorityRepository authorityRepository, + ConfigurationRepository configurationRepository) { this.userRepository = userRepository; this.authorityRepository = authorityRepository; + this.configurationRepository = configurationRepository; } //https://stackoverflow.com/a/48448901 - @Override - protected void configure(HttpSecurity http) throws Exception { + @Bean + public SecurityFilterChain configure(HttpSecurity http) throws Exception { APIKeyAuthFilter filter = new APIKeyAuthFilter(); filter.setAuthenticationManager(authentication -> { // String apiKey = (String) authentication.getPrincipal(); + + // check if API Key is system + var systemApiKeyOptional = configurationRepository.findOptionalByKey(ConfigurationKeys.SYSTEM_API_KEY.name()); + + if (systemApiKeyOptional.isPresent() && apiKeyMatches(apiKey, systemApiKeyOptional.get())) { + return new APITokenAuthentication( + authentication.getPrincipal(), + authentication.getCredentials(), + List.of(new SimpleGrantedAuthority("ROLE_" + SYSTEM_API_CLIENT))); + } + //check if user type -> - User user = userRepository.findByUsername(apiKey).orElseThrow(() -> new BadCredentialsException("Api key " + apiKey + " don't exists")); + User user = userRepository.findByUsername(apiKey).orElseThrow(() -> new BadCredentialsException(API_KEY + apiKey + " don't exists")); if (!user.isEnabled()) { - throw new DisabledException("Api key " + apiKey + " is disabled"); + throw new DisabledException(API_KEY + apiKey + " is disabled"); } if (User.Type.API_KEY != user.getType()) { throw new WrongAccountTypeException("Wrong account type for username " + apiKey); } if (!user.isCurrentlyValid(ZonedDateTime.now(ClockProvider.clock()))) { - throw new DisabledException("Api key " + apiKey + " is expired"); + throw new DisabledException(API_KEY + apiKey + " is expired"); } return new APITokenAuthentication( authentication.getPrincipal(), authentication.getCredentials(), - authorityRepository.findRoles(apiKey).stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); + authorityRepository.findRoles(apiKey).stream().map(SimpleGrantedAuthority::new).toList()); }); - http.requestMatcher(RequestTypeMatchers::isTokenAuthentication) + return http.requestMatcher(RequestTypeMatchers::isTokenAuthentication) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().csrf().disable() - .authorizeRequests() + .authorizeRequests(APITokenAuthWebSecurity::configureMatchers) + .addFilter(filter) + .build(); + } + + private static void configureMatchers(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry auth) { + auth.antMatchers(ADMIN_PUBLIC_API + "/system/**").hasRole(SYSTEM_API_CLIENT) .antMatchers(ADMIN_PUBLIC_API + "/**").hasRole(API_CLIENT) + .antMatchers(ADMIN_API + "/check-in/event/*/attendees").hasRole(SUPERVISOR) + .antMatchers(ADMIN_API + "/check-in/*/label-layout").hasAnyRole(OPERATOR, SUPERVISOR, SPONSOR) .antMatchers(ADMIN_API + "/check-in/**").hasAnyRole(OPERATOR, SUPERVISOR) - .antMatchers(HttpMethod.GET, ADMIN_API + "/events").hasAnyRole(OPERATOR, SUPERVISOR, AuthenticationConstants.SPONSOR) + .antMatchers(HttpMethod.GET, ADMIN_API + "/events").hasAnyRole(OPERATOR, SUPERVISOR, SPONSOR) .antMatchers(HttpMethod.GET, ADMIN_API + "/user-type", ADMIN_API + "/user/details").hasAnyRole(OPERATOR, SUPERVISOR, AuthenticationConstants.SPONSOR) .antMatchers(ADMIN_API + "/**").denyAll() - .antMatchers(HttpMethod.POST, "/api/attendees/sponsor-scan").hasRole(AuthenticationConstants.SPONSOR) + .antMatchers(HttpMethod.POST, "/api/attendees/sponsor-scan").hasRole(SPONSOR) .antMatchers(HttpMethod.GET, "/api/attendees/*/ticket/*").hasAnyRole(OPERATOR, SUPERVISOR, API_CLIENT) - .antMatchers("/**").authenticated() - .and().addFilter(filter); + .antMatchers("/**").authenticated(); + } + + private static boolean apiKeyMatches(String input, alfio.model.system.Configuration systemApiKeyConfiguration) { + return MessageDigest.isEqual(input.getBytes(StandardCharsets.UTF_8), + systemApiKeyConfiguration.getValue().getBytes(StandardCharsets.UTF_8)); } } diff --git a/src/main/java/alfio/config/authentication/AbstractFormBasedWebSecurity.java b/src/main/java/alfio/config/authentication/AbstractFormBasedWebSecurity.java index e0cd288a29..39e7bd12ce 100644 --- a/src/main/java/alfio/config/authentication/AbstractFormBasedWebSecurity.java +++ b/src/main/java/alfio/config/authentication/AbstractFormBasedWebSecurity.java @@ -23,17 +23,21 @@ import alfio.manager.openid.PublicOpenIdAuthenticationManager; import alfio.manager.system.ConfigurationManager; import alfio.manager.user.UserManager; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jetty.http.HttpCookie; +import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.JdbcUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.csrf.CsrfToken; @@ -41,20 +45,45 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; +import javax.servlet.Filter; import javax.servlet.RequestDispatcher; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; +import java.util.List; import java.util.function.Predicate; import java.util.regex.Pattern; -import static alfio.config.authentication.AuthenticationConstants.*; +import static alfio.config.Initializer.API_V2_PUBLIC_PATH; +import static alfio.config.Initializer.XSRF_TOKEN; +import static alfio.config.authentication.support.AuthenticationConstants.*; import static alfio.config.authentication.support.OpenIdAuthenticationFilter.*; -@AllArgsConstructor(access = AccessLevel.PROTECTED) -abstract class AbstractFormBasedWebSecurity extends WebSecurityConfigurerAdapter { +abstract class AbstractFormBasedWebSecurity { + public static final String AUTHENTICATE = "/authenticate"; + private static final String[] OWNERSHIP_REQUIRED = new String[]{ + ADMIN_API + "/overridable-template", + ADMIN_API + "/additional-services", + ADMIN_API + "/events/*/additional-field", + ADMIN_API + "/event/*/additional-services/", + ADMIN_API + "/overridable-template/", + ADMIN_API + "/events/*/promo-code", + ADMIN_API + "/reservation/event/*/reservations/list", + ADMIN_API + "/event/*/email/", + ADMIN_API + "/event/*/waiting-queue/load", + ADMIN_API + "/events/*/pending-payments", + ADMIN_API + "/events/*/export", + ADMIN_API + "/events/*/sponsor-scan/export", + ADMIN_API + "/events/*/invoices/**", + ADMIN_API + "/reservation/*/*/*/audit", + ADMIN_API + "/subscription/*/email/", + ADMIN_API + "/organization/*/subscription/**", + ADMIN_API + "/reservation/subscription/**" + }; private final Environment environment; private final UserManager userManager; private final RecaptchaService recaptchaService; @@ -63,149 +92,187 @@ abstract class AbstractFormBasedWebSecurity extends WebSecurityConfigurerAdapter private final DataSource dataSource; private final PasswordEncoder passwordEncoder; private final PublicOpenIdAuthenticationManager publicOpenIdAuthenticationManager; + private final SpringSessionBackedSessionRegistry<?> sessionRegistry; - @Override - public void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.jdbcAuthentication().dataSource(dataSource) - .usersByUsernameQuery("select username, password, enabled from ba_user where username = ?") - .authoritiesByUsernameQuery("select username, role from authority where username = ?") - .passwordEncoder(passwordEncoder) - .and() - .authenticationProvider(new OpenIdAuthenticationProvider()); + protected AbstractFormBasedWebSecurity(Environment environment, + UserManager userManager, + RecaptchaService recaptchaService, + ConfigurationManager configurationManager, + CsrfTokenRepository csrfTokenRepository, + DataSource dataSource, + PasswordEncoder passwordEncoder, + PublicOpenIdAuthenticationManager publicOpenIdAuthenticationManager, + SpringSessionBackedSessionRegistry<?> sessionRegistry) { + this.environment = environment; + this.userManager = userManager; + this.recaptchaService = recaptchaService; + this.configurationManager = configurationManager; + this.csrfTokenRepository = csrfTokenRepository; + this.dataSource = dataSource; + this.passwordEncoder = passwordEncoder; + this.publicOpenIdAuthenticationManager = publicOpenIdAuthenticationManager; + this.sessionRegistry = sessionRegistry; } - @Override - protected void configure(HttpSecurity http) throws Exception { - if (environment.acceptsProfiles(Profiles.of("!" + Initializer.PROFILE_DEV))) { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + if (environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_LIVE))) { http.requiresChannel().antMatchers("/healthz").requiresInsecure() .and() .requiresChannel().mvcMatchers("/**").requiresSecure(); } - CsrfConfigurer<HttpSecurity> configurer = - http.exceptionHandling() - .accessDeniedHandler((request, response, accessDeniedException) -> { - if (!response.isCommitted()) { - if ("XMLHttpRequest".equals(request.getHeader(AuthenticationConstants.X_REQUESTED_WITH))) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - } else if (!response.isCommitted()) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - RequestDispatcher dispatcher = request.getRequestDispatcher("/session-expired"); - dispatcher.forward(request, response); - } - } - }) - .defaultAuthenticationEntryPointFor((request, response, ex) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED), new RequestHeaderRequestMatcher(AuthenticationConstants.X_REQUESTED_WITH, "XMLHttpRequest")) - .and() - .headers().cacheControl().disable() - .and() - .csrf(); - - Pattern pattern = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$"); - Predicate<HttpServletRequest> csrfWhitelistPredicate = r -> r.getRequestURI().startsWith("/api/webhook/") - || r.getRequestURI().startsWith("/api/payment/webhook/") - || pattern.matcher(r.getMethod()).matches(); - csrfWhitelistPredicate = csrfWhitelistPredicate.or(r -> r.getRequestURI().equals("/report-csp-violation")); - configurer.requireCsrfProtectionMatcher(new NegatedRequestMatcher(csrfWhitelistPredicate::test)); - - String[] ownershipRequired = new String[]{ - ADMIN_API + "/overridable-template", - ADMIN_API + "/additional-services", - ADMIN_API + "/events/*/additional-field", - ADMIN_API + "/event/*/additional-services/", - ADMIN_API + "/overridable-template/", - ADMIN_API + "/events/*/promo-code", - ADMIN_API + "/reservation/event/*/reservations/list", - ADMIN_API + "/event/*/email/", - ADMIN_API + "/event/*/waiting-queue/load", - ADMIN_API + "/events/*/pending-payments", - ADMIN_API + "/events/*/export", - ADMIN_API + "/events/*/sponsor-scan/export", - ADMIN_API + "/events/*/invoices/**", - ADMIN_API + "/reservation/*/*/*/audit", - ADMIN_API + "/subscription/*/email/", - ADMIN_API + "/organization/*/subscription/**", - ADMIN_API + "/reservation/subscription/**" - }; + CsrfConfigurer<HttpSecurity> configurer = csrfConfigurer(http); + var authenticationManager = createAuthenticationManager(); configurer.csrfTokenRepository(csrfTokenRepository) .and() .headers().frameOptions().disable() // https://github.com/alfio-event/alf.io/issues/1031 X-Frame-Options has been moved to IndexController .and() - .authorizeRequests() - .antMatchers(HttpMethod.GET, ADMIN_API + "/users/current").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers(HttpMethod.POST, ADMIN_API + "/users/check", ADMIN_API + "/users/current/edit", ADMIN_API + "/users/current/update-password").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers(ADMIN_API + "/configuration/**", ADMIN_API + "/users/**").hasAnyRole(ADMIN, OWNER) - .antMatchers(ADMIN_API + "/organizations/new").hasRole(ADMIN) - .antMatchers(ADMIN_API + "/check-in/**").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers(HttpMethod.GET, ownershipRequired).hasAnyRole(ADMIN, OWNER) - .antMatchers(HttpMethod.GET, ADMIN_API + "/**").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers(HttpMethod.POST, ADMIN_API + "/reservation/event/*/new", ADMIN_API + "/reservation/event/*/*").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers(HttpMethod.PUT, ADMIN_API + "/reservation/event/*/*/notify", ADMIN_API + "/reservation/event/*/*/confirm").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers(ADMIN_API + "/**").hasAnyRole(ADMIN, OWNER) - .antMatchers("/admin/**/export/**").hasAnyRole(ADMIN, OWNER) - .antMatchers("/admin/**").hasAnyRole(ADMIN, OWNER, SUPERVISOR) - .antMatchers("/api/attendees/**").denyAll() - .antMatchers("/callback").permitAll() - .antMatchers("/**").permitAll() - .and() + .authorizeRequests(AbstractFormBasedWebSecurity::authorizeRequests) + .authenticationManager(authenticationManager) .formLogin() .loginPage("/authentication") - .loginProcessingUrl("/authenticate") + .loginProcessingUrl(AUTHENTICATE) + .defaultSuccessUrl("/admin") .failureUrl("/authentication?failed") - .and().logout().permitAll(); + .and().logout().permitAll() + .and() + // this allows us to sync between spring session and spring security, thus saving the principal name in the session table + .sessionManagement().maximumSessions(-1).sessionRegistry(sessionRegistry); - http.addFilterBefore(openIdPublicCallbackLoginFilter(publicOpenIdAuthenticationManager), UsernamePasswordAuthenticationFilter.class) + http.addFilterBefore(openIdPublicCallbackLoginFilter(publicOpenIdAuthenticationManager, authenticationManager), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(openIdPublicAuthenticationFilter(publicOpenIdAuthenticationManager), AnonymousAuthenticationFilter.class); // - http.addFilterBefore(new RecaptchaLoginFilter(recaptchaService, "/authenticate", "/authentication?recaptchaFailed", configurationManager), UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(new RecaptchaLoginFilter(recaptchaService, AUTHENTICATE, "/authentication?recaptchaFailed", configurationManager), UsernamePasswordAuthenticationFilter.class); // call implementation-specific logic - addAdditionalFilters(http); + addAdditionalFilters(http, authenticationManager); + + http.addFilterBefore(csrfFilter(), RecaptchaLoginFilter.class); + + if (environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_DEMO))) { + http.addFilterAfter(new UserCreatorBeforeLoginFilter(userManager, AUTHENTICATE), RecaptchaLoginFilter.class); + } + + return http.build(); + } + + private JdbcUserDetailsManager createUserDetailsManager() { + var userDetailsManager = new JdbcUserDetailsManager(dataSource); + userDetailsManager.setUsersByUsernameQuery("select username, password, enabled from ba_user where username = ?"); + userDetailsManager.setAuthoritiesByUsernameQuery("select username, role from authority where username = ?"); + return userDetailsManager; + } + + private AuthenticationManager createAuthenticationManager() { + var userDetailsManager = createUserDetailsManager(); + var daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); + daoAuthenticationProvider.setUserDetailsService(userDetailsManager); + return new ProviderManager(List.of(daoAuthenticationProvider, new OpenIdAuthenticationProvider())); + } - //FIXME create session and set csrf cookie if we are getting a v2 public api, an admin api call , will switch to pure cookie based - http.addFilterBefore((servletRequest, servletResponse, filterChain) -> { + private Filter csrfFilter() { + return (servletRequest, servletResponse, filterChain) -> { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse res = (HttpServletResponse) servletResponse; var reqUri = req.getRequestURI(); - if ((reqUri.startsWith("/api/v2/public/") || reqUri.startsWith("/admin/api/") || reqUri.startsWith("/api/v2/admin/")) && "GET".equalsIgnoreCase(req.getMethod())) { + if ((reqUri.startsWith(API_V2_PUBLIC_PATH) || reqUri.startsWith(ADMIN_API) || reqUri.startsWith("/api/v2/admin/") || reqUri.equals(AUTHENTICATION_STATUS)) && "GET".equalsIgnoreCase(req.getMethod())) { CsrfToken csrf = csrfTokenRepository.loadToken(req); if (csrf == null) { csrf = csrfTokenRepository.generateToken(req); } - Cookie cookie = new Cookie("XSRF-TOKEN", csrf.getToken()); - cookie.setPath("/"); - res.addCookie(cookie); + res.addHeader(XSRF_TOKEN, csrf.getToken()); + if (!reqUri.startsWith(API_V2_PUBLIC_PATH)) { + // FIXME remove this after the new admin is complete + addCookie(res, csrf); + } } filterChain.doFilter(servletRequest, servletResponse); - }, RecaptchaLoginFilter.class); + }; + } - if (environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_DEMO))) { - http.addFilterAfter(new UserCreatorBeforeLoginFilter(userManager, "/authenticate"), RecaptchaLoginFilter.class); + private static void authorizeRequests(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry auth) { + auth.antMatchers(ADMIN_PUBLIC_API + "/**").denyAll() // Admin public API requests must be authenticated using API-Keys + .antMatchers(HttpMethod.GET, ADMIN_API + "/users/current").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers(HttpMethod.POST, ADMIN_API + "/users/check", ADMIN_API + "/users/current/edit", ADMIN_API + "/users/current/update-password").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers(ADMIN_API + "/configuration/**", ADMIN_API + "/users/**").hasAnyRole(ADMIN, OWNER) + .antMatchers(ADMIN_API + "/organizations/new", ADMIN_API + "/system/**").hasRole(ADMIN) + .antMatchers(ADMIN_API + "/check-in/**").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers(HttpMethod.GET, OWNERSHIP_REQUIRED).hasAnyRole(ADMIN, OWNER) + .antMatchers(HttpMethod.GET, ADMIN_API + "/**").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers(HttpMethod.POST, ADMIN_API + "/reservation/event/*/new", ADMIN_API + "/reservation/event/*/*").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers(HttpMethod.PUT, ADMIN_API + "/reservation/event/*/*/notify", ADMIN_API + "/reservation/event/*/*/confirm").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers(ADMIN_API + "/**").hasAnyRole(ADMIN, OWNER) + .antMatchers("/admin/**/export/**").hasAnyRole(ADMIN, OWNER) + .antMatchers("/admin/**").hasAnyRole(ADMIN, OWNER, SUPERVISOR) + .antMatchers("/api/attendees/**").denyAll() + .antMatchers("/callback").permitAll() + .antMatchers("/**").permitAll(); + } + + private static CsrfConfigurer<HttpSecurity> csrfConfigurer(HttpSecurity http) throws Exception { + var configurer = http.exceptionHandling() + .accessDeniedHandler((request, response, accessDeniedException) -> { + if (!response.isCommitted()) { + if ("XMLHttpRequest".equals(request.getHeader(AuthenticationConstants.X_REQUESTED_WITH))) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } else if (!response.isCommitted()) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + RequestDispatcher dispatcher = request.getRequestDispatcher("/session-expired"); + dispatcher.forward(request, response); + } + } + }) + .defaultAuthenticationEntryPointFor((request, response, ex) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED), new RequestHeaderRequestMatcher(AuthenticationConstants.X_REQUESTED_WITH, "XMLHttpRequest")) + .and() + .headers().cacheControl().disable() + .and() + .csrf(); + Pattern methodsPattern = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$"); + Predicate<HttpServletRequest> csrfAllowListPredicate = r -> r.getRequestURI().startsWith("/api/webhook/") + || r.getRequestURI().startsWith("/api/payment/webhook/") + || methodsPattern.matcher(r.getMethod()).matches(); + csrfAllowListPredicate = csrfAllowListPredicate.or(r -> r.getRequestURI().equals("/report-csp-violation")); + configurer.requireCsrfProtectionMatcher(new NegatedRequestMatcher(csrfAllowListPredicate::test)); + return configurer; + } + + private void addCookie(HttpServletResponse res, CsrfToken csrf) { + Cookie cookie = new Cookie(XSRF_TOKEN, csrf.getToken()); + cookie.setPath("/"); + boolean prod = environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_LIVE)); + cookie.setSecure(prod); + if (prod) { + cookie.setComment(HttpCookie.SAME_SITE_STRICT_COMMENT); } + res.addCookie(cookie); } /** * This method is called right after applying the {@link RecaptchaLoginFilter} * * @param http + * @param jdbcAuthenticationManager */ - protected void addAdditionalFilters(HttpSecurity http) throws Exception { + protected void addAdditionalFilters(HttpSecurity http, AuthenticationManager jdbcAuthenticationManager) { } private OpenIdAuthenticationFilter openIdPublicAuthenticationFilter(OpenIdAuthenticationManager openIdAuthenticationManager) { return new OpenIdAuthenticationFilter("/openid/authentication", openIdAuthenticationManager, "/", true); } - private OpenIdCallbackLoginFilter openIdPublicCallbackLoginFilter(OpenIdAuthenticationManager openIdAuthenticationManager) throws Exception { + private OpenIdCallbackLoginFilter openIdPublicCallbackLoginFilter(OpenIdAuthenticationManager openIdAuthenticationManager, + AuthenticationManager jdbcAuthenticationManager) { var filter = new OpenIdCallbackLoginFilter(openIdAuthenticationManager, new AntPathRequestMatcher("/openid/callback", "GET"), - authenticationManager()); + jdbcAuthenticationManager); filter.setAuthenticationSuccessHandler((request, response, authentication) -> { var session = request.getSession(); var reservationId = (String) session.getAttribute(RESERVATION_KEY); diff --git a/src/main/java/alfio/config/authentication/FormBasedWebSecurity.java b/src/main/java/alfio/config/authentication/FormBasedWebSecurity.java index b9372dd76b..95087317e4 100644 --- a/src/main/java/alfio/config/authentication/FormBasedWebSecurity.java +++ b/src/main/java/alfio/config/authentication/FormBasedWebSecurity.java @@ -26,6 +26,7 @@ import org.springframework.core.env.Environment; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; import javax.sql.DataSource; @@ -43,7 +44,8 @@ public FormBasedWebSecurity(Environment environment, CsrfTokenRepository csrfTokenRepository, DataSource dataSource, PasswordEncoder passwordEncoder, - PublicOpenIdAuthenticationManager publicOpenIdAuthenticationManager) { + PublicOpenIdAuthenticationManager publicOpenIdAuthenticationManager, + SpringSessionBackedSessionRegistry<?> sessionRegistry) { super(environment, userManager, recaptchaService, @@ -51,6 +53,7 @@ public FormBasedWebSecurity(Environment environment, csrfTokenRepository, dataSource, passwordEncoder, - publicOpenIdAuthenticationManager); + publicOpenIdAuthenticationManager, + sessionRegistry); } } diff --git a/src/main/java/alfio/config/authentication/OpenIdAdminWebSecurity.java b/src/main/java/alfio/config/authentication/OpenIdAdminWebSecurity.java index 7fd52cd1f0..b2bffe46de 100644 --- a/src/main/java/alfio/config/authentication/OpenIdAdminWebSecurity.java +++ b/src/main/java/alfio/config/authentication/OpenIdAdminWebSecurity.java @@ -23,25 +23,29 @@ import alfio.manager.openid.PublicOpenIdAuthenticationManager; import alfio.manager.system.ConfigurationManager; import alfio.manager.user.UserManager; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.session.security.SpringSessionBackedSessionRegistry; import javax.sql.DataSource; @Profile("openid") @Configuration(proxyBeanMethods = false) @Order(1) -@Log4j2 public class OpenIdAdminWebSecurity extends AbstractFormBasedWebSecurity { + private static final Logger log = LoggerFactory.getLogger(OpenIdAdminWebSecurity.class); + private final AdminOpenIdAuthenticationManager adminOpenIdAuthenticationManager; public OpenIdAdminWebSecurity(Environment environment, @@ -52,7 +56,8 @@ public OpenIdAdminWebSecurity(Environment environment, DataSource dataSource, PasswordEncoder passwordEncoder, AdminOpenIdAuthenticationManager adminOpenIdAuthenticationManager, - PublicOpenIdAuthenticationManager openIdAuthenticationManager) { + PublicOpenIdAuthenticationManager openIdAuthenticationManager, + SpringSessionBackedSessionRegistry<?> sessionRegistry) { super(environment, userManager, recaptchaService, @@ -60,15 +65,16 @@ public OpenIdAdminWebSecurity(Environment environment, csrfTokenRepository, dataSource, passwordEncoder, - openIdAuthenticationManager); + openIdAuthenticationManager, + sessionRegistry); this.adminOpenIdAuthenticationManager = adminOpenIdAuthenticationManager; } @Override - protected void addAdditionalFilters(HttpSecurity http) throws Exception { + protected void addAdditionalFilters(HttpSecurity http, AuthenticationManager jdbcAuthenticationManager) { var callbackLoginFilter = new OpenIdCallbackLoginFilter(adminOpenIdAuthenticationManager, new AntPathRequestMatcher("/callback", "GET"), - authenticationManager()); + jdbcAuthenticationManager); http.addFilterBefore(callbackLoginFilter, UsernamePasswordAuthenticationFilter.class); log.trace("adding openid filter"); http.addFilterAfter(new OpenIdAuthenticationFilter("/authentication", adminOpenIdAuthenticationManager, "/", false), OpenIdCallbackLoginFilter.class); diff --git a/src/main/java/alfio/config/authentication/support/APIKeyAuthFilter.java b/src/main/java/alfio/config/authentication/support/APIKeyAuthFilter.java index 80561ab300..df39504f67 100644 --- a/src/main/java/alfio/config/authentication/support/APIKeyAuthFilter.java +++ b/src/main/java/alfio/config/authentication/support/APIKeyAuthFilter.java @@ -27,7 +27,10 @@ public class APIKeyAuthFilter extends AbstractPreAuthenticatedProcessingFilter { @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { - return isTokenAuthentication(request) ? StringUtils.trim(request.getHeader("Authorization").substring("apikey ".length())) : null; + if(!isTokenAuthentication(request) || StringUtils.isBlank(request.getHeader("Authorization"))) { + return null; + } + return StringUtils.trim(request.getHeader("Authorization").substring("apikey ".length())); } @Override diff --git a/src/main/java/alfio/config/authentication/support/AuthenticationConstants.java b/src/main/java/alfio/config/authentication/support/AuthenticationConstants.java new file mode 100644 index 0000000000..3cf3fa40e8 --- /dev/null +++ b/src/main/java/alfio/config/authentication/support/AuthenticationConstants.java @@ -0,0 +1,35 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.config.authentication.support; + +public final class AuthenticationConstants { + + private AuthenticationConstants() { + } + + public static final String OPERATOR = "OPERATOR"; + public static final String SPONSOR = "SPONSOR"; + public static final String ADMIN_API = "/admin/api"; + public static final String ADMIN_PUBLIC_API = "/api/v1/admin"; + public static final String SUPERVISOR = "SUPERVISOR"; + public static final String ADMIN = "ADMIN"; + public static final String OWNER = "OWNER"; + public static final String API_CLIENT = "API_CLIENT"; + public static final String SYSTEM_API_CLIENT = "SYSTEM_API_CLIENT"; + public static final String X_REQUESTED_WITH = "X-Requested-With"; + public static final String AUTHENTICATION_STATUS = "/authentication-status"; +} diff --git a/src/main/java/alfio/config/authentication/support/OpenIdAlfioUser.java b/src/main/java/alfio/config/authentication/support/OpenIdAlfioUser.java index 68391d7062..a4b8dd250b 100644 --- a/src/main/java/alfio/config/authentication/support/OpenIdAlfioUser.java +++ b/src/main/java/alfio/config/authentication/support/OpenIdAlfioUser.java @@ -18,14 +18,12 @@ import alfio.model.user.Role; import alfio.model.user.User; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Map; import java.util.Set; @Getter -@AllArgsConstructor public class OpenIdAlfioUser { private final String idToken; private final String subject; @@ -34,6 +32,20 @@ public class OpenIdAlfioUser { private final Set<Role> alfioRoles; private final Map<String, Set<String>> alfioOrganizationAuthorizations; + public OpenIdAlfioUser(String idToken, + String subject, + String email, + User.Type userType, + Set<Role> alfioRoles, + Map<String, Set<String>> alfioOrganizationAuthorizations) { + this.idToken = idToken; + this.subject = subject; + this.email = email; + this.userType = userType; + this.alfioRoles = alfioRoles; + this.alfioOrganizationAuthorizations = alfioOrganizationAuthorizations; + } + public boolean isAdmin() { return userType == User.Type.INTERNAL && alfioRoles.contains(Role.ADMIN); } diff --git a/src/main/java/alfio/config/authentication/support/OpenIdAuthenticationFilter.java b/src/main/java/alfio/config/authentication/support/OpenIdAuthenticationFilter.java index 5423dab443..baa4921cd5 100644 --- a/src/main/java/alfio/config/authentication/support/OpenIdAuthenticationFilter.java +++ b/src/main/java/alfio/config/authentication/support/OpenIdAuthenticationFilter.java @@ -17,7 +17,8 @@ package alfio.config.authentication.support; import alfio.manager.openid.OpenIdAuthenticationManager; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -32,8 +33,10 @@ import java.io.IOException; import java.util.UUID; -@Log4j2 public class OpenIdAuthenticationFilter extends GenericFilterBean { + + private static final Logger log = LoggerFactory.getLogger(OpenIdAuthenticationFilter.class); + public static final String RESERVATION_KEY = "RESERVATION"; public static final String CONTEXT_TYPE_KEY = "CONTEXT_TYPE"; public static final String CONTEXT_ID_KEY = "CONTEXT_ID"; diff --git a/src/main/java/alfio/config/authentication/support/OpenIdCallbackLoginFilter.java b/src/main/java/alfio/config/authentication/support/OpenIdCallbackLoginFilter.java index ebb44b4547..9509218401 100644 --- a/src/main/java/alfio/config/authentication/support/OpenIdCallbackLoginFilter.java +++ b/src/main/java/alfio/config/authentication/support/OpenIdCallbackLoginFilter.java @@ -17,7 +17,8 @@ package alfio.config.authentication.support; import alfio.manager.openid.OpenIdAuthenticationManager; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -34,9 +35,10 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; -@Log4j2 public class OpenIdCallbackLoginFilter extends AbstractAuthenticationProcessingFilter { + private static final Logger log = LoggerFactory.getLogger(OpenIdCallbackLoginFilter.class); + private final RequestMatcher requestMatcher; private final OpenIdAuthenticationManager openIdAuthenticationManager; diff --git a/src/main/java/alfio/config/authentication/support/RequestTypeMatchers.java b/src/main/java/alfio/config/authentication/support/RequestTypeMatchers.java index 720c4f6321..938784dd40 100644 --- a/src/main/java/alfio/config/authentication/support/RequestTypeMatchers.java +++ b/src/main/java/alfio/config/authentication/support/RequestTypeMatchers.java @@ -16,15 +16,17 @@ */ package alfio.config.authentication.support; -import lombok.experimental.UtilityClass; - import javax.servlet.http.HttpServletRequest; import java.util.Locale; -@UtilityClass -public class RequestTypeMatchers { +public final class RequestTypeMatchers { + + private RequestTypeMatchers() { + } + public static boolean isTokenAuthentication(HttpServletRequest request) { String authorization = request.getHeader("Authorization"); - return authorization != null && authorization.toLowerCase(Locale.ENGLISH).startsWith("apikey "); + return (authorization != null && authorization.toLowerCase(Locale.ENGLISH).startsWith("apikey ")) + || request.getRequestURI().startsWith(AuthenticationConstants.ADMIN_PUBLIC_API); } } diff --git a/src/main/java/alfio/config/authentication/support/UserCreatorBeforeLoginFilter.java b/src/main/java/alfio/config/authentication/support/UserCreatorBeforeLoginFilter.java index fefe96d6c7..a7c2ddc642 100644 --- a/src/main/java/alfio/config/authentication/support/UserCreatorBeforeLoginFilter.java +++ b/src/main/java/alfio/config/authentication/support/UserCreatorBeforeLoginFilter.java @@ -30,8 +30,9 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.util.UUID; -// generate a user if it does not exists, to be used by the demo profile +// generate a user if it does not exist, to be used by the demo profile public class UserCreatorBeforeLoginFilter extends GenericFilterBean { private final UserManager userManager; @@ -51,9 +52,9 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha if (requestMatcher.matches(req) && req.getParameter("username") != null && req.getParameter("password") != null) { String username = req.getParameter("username"); if (!userManager.usernameExists(username)) { - var organizationModification = new OrganizationModification(null, "Demo organization", username, username, null, null); - int orgId = userManager.createOrganization(organizationModification); - userManager.insertUser(orgId, username, "", "", username, Role.OWNER, User.Type.DEMO, req.getParameter("password"), null, null); + var organizationModification = new OrganizationModification(null, UUID.randomUUID().toString(), username, username, null, null); + int orgId = userManager.createOrganization(organizationModification, null); + userManager.insertUser(orgId, username, "", "", username, Role.OWNER, User.Type.DEMO, req.getParameter("password"), null, null, null); } } diff --git a/src/main/java/alfio/config/support/ArrayColumnMapper.java b/src/main/java/alfio/config/support/ArrayColumnMapper.java index 9983e8707f..a4795a3002 100644 --- a/src/main/java/alfio/config/support/ArrayColumnMapper.java +++ b/src/main/java/alfio/config/support/ArrayColumnMapper.java @@ -20,7 +20,6 @@ import ch.digitalfondue.npjt.mapper.ColumnMapper; import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; import ch.digitalfondue.npjt.mapper.ParameterConverter; -import lombok.SneakyThrows; import org.springframework.jdbc.core.RowMapper; import java.lang.annotation.Annotation; @@ -90,7 +89,6 @@ public boolean accept(Class<?> parameterType, Annotation[] annotations) { return hasAnnotation(annotations) && List.class.isAssignableFrom(parameterType); } - @SneakyThrows @Override public void processParameter(ProcessParameterContext processParameterContext) { var arg = processParameterContext.getArg(); @@ -99,8 +97,12 @@ public void processParameter(ProcessParameterContext processParameterContext) { ps.addValue(processParameterContext.getParameterName(), null, Types.ARRAY); } else { Array def = (Array) Arrays.stream(processParameterContext.getParameterAnnotations()).filter(ArrayColumnMapper::annotationFinder).findFirst().orElseThrow(); - var array = processParameterContext.getConnection().createArrayOf(def.type(), ((List<?>) arg).toArray()); - ps.addValue(processParameterContext.getParameterName(), array); + try { + var array = processParameterContext.getConnection().createArrayOf(def.type(), ((List<?>) arg).toArray()); + ps.addValue(processParameterContext.getParameterName(), array); + } catch (SQLException e) { + throw new IllegalStateException(e); + } } } diff --git a/src/main/java/alfio/config/support/EnumTypeColumnMapper.java b/src/main/java/alfio/config/support/EnumTypeColumnMapper.java index 737cef9dee..09e45b8ca0 100644 --- a/src/main/java/alfio/config/support/EnumTypeColumnMapper.java +++ b/src/main/java/alfio/config/support/EnumTypeColumnMapper.java @@ -20,7 +20,6 @@ import ch.digitalfondue.npjt.mapper.ColumnMapper; import ch.digitalfondue.npjt.mapper.ColumnMapperFactory; import ch.digitalfondue.npjt.mapper.ParameterConverter; -import lombok.extern.log4j.Log4j2; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -31,7 +30,6 @@ import java.util.Arrays; import java.util.Objects; -@Log4j2 public class EnumTypeColumnMapper extends ColumnMapper { private static final int ORDER = Integer.MAX_VALUE - 32; diff --git a/src/main/java/alfio/controller/AdminConfigurationController.java b/src/main/java/alfio/controller/AdminConfigurationController.java index b4750223ca..930fdd398a 100644 --- a/src/main/java/alfio/controller/AdminConfigurationController.java +++ b/src/main/java/alfio/controller/AdminConfigurationController.java @@ -21,9 +21,9 @@ import alfio.manager.payment.StripeConnectManager; import alfio.manager.user.UserManager; import alfio.util.oauth2.AccessTokenResponseDetails; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -41,10 +41,10 @@ import static alfio.manager.payment.StripeConnectManager.STRIPE_CONNECT_REDIRECT_PATH; @Controller -@AllArgsConstructor -@Log4j2 public class AdminConfigurationController { + private static final Logger log = LoggerFactory.getLogger(AdminConfigurationController.class); + private static final String CONNECT_ORG = ".connect.org"; private static final String CONNECT_STATE_PREFIX = ".connect.state."; private static final String REDIRECT_ADMIN = "redirect:/admin/"; @@ -54,6 +54,14 @@ public class AdminConfigurationController { private final MollieConnectManager mollieConnectManager; private final UserManager userManager; + public AdminConfigurationController(StripeConnectManager stripeConnectManager, + MollieConnectManager mollieConnectManager, + UserManager userManager) { + this.stripeConnectManager = stripeConnectManager; + this.mollieConnectManager = mollieConnectManager; + this.userManager = userManager; + } + @GetMapping("/admin/configuration/payment/{provider}/connect/{orgId}") public String oAuthRedirectToAuthorizationURL(Principal principal, @PathVariable("orgId") Integer orgId, @@ -61,9 +69,9 @@ public String oAuthRedirectToAuthorizationURL(Principal principal, HttpSession session) { if(CONNECT_PROVIDERS.contains(provider) && userManager.isOwnerOfOrganization(userManager.findUserByUsername(principal.getName()), orgId)) { var connectURL = getConnector(provider).getConnectURL(orgId); - session.setAttribute(provider+CONNECT_STATE_PREFIX +orgId, connectURL.getState()); + session.setAttribute(provider+CONNECT_STATE_PREFIX +orgId, connectURL.state()); session.setAttribute(provider+CONNECT_ORG, orgId); - return "redirect:" + connectURL.getAuthorizationUrl(); + return "redirect:" + connectURL.authorizationUrl(); } return REDIRECT_ADMIN; } @@ -92,7 +100,7 @@ public String authorize(Principal principal, boolean stateVerified = Objects.equals(persistedState, state); if(stateVerified && code != null) { AccessTokenResponseDetails connectResult = getConnector(provider).storeConnectedAccountId(code, orgId); - if(connectResult.isSuccess()) { + if(connectResult.success()) { return "redirect:/admin/#/configuration/organization/"+orgId; } } else if(stateVerified && StringUtils.isNotEmpty(errorCode)) { diff --git a/src/main/java/alfio/controller/AdminIndexController.java b/src/main/java/alfio/controller/AdminIndexController.java new file mode 100644 index 0000000000..cc7b1a4e5e --- /dev/null +++ b/src/main/java/alfio/controller/AdminIndexController.java @@ -0,0 +1,95 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller; + +import alfio.config.authentication.support.OpenIdAlfioAuthentication; +import alfio.controller.support.CSPConfigurer; +import alfio.manager.system.ConfigurationManager; +import alfio.model.user.Role; +import alfio.util.TemplateManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; +import java.util.Collection; + +import static alfio.controller.Constants.*; +import static alfio.controller.Constants.NONCE; +import static alfio.model.system.ConfigurationKeys.SHOW_PROJECT_BANNER; + +@Controller +public class AdminIndexController { + + private final ConfigurationManager configurationManager; + private final Environment environment; + private final CSPConfigurer cspConfigurer; + private final TemplateManager templateManager; + + public AdminIndexController(ConfigurationManager configurationManager, + Environment environment, + CSPConfigurer cspConfigurer, + TemplateManager templateManager) { + this.configurationManager = configurationManager; + this.environment = environment; + this.cspConfigurer = cspConfigurer; + this.templateManager = templateManager; + } + + @GetMapping("/admin") + public void adminHome(Model model, @Value("${alfio.version}") String version, HttpServletRequest request, HttpServletResponse response, Principal principal) throws IOException { + model.addAttribute("alfioVersion", version); + model.addAttribute("username", principal.getName()); + model.addAttribute("basicConfigurationNeeded", configurationManager.isBasicConfigurationNeeded()); + + boolean isDBAuthentication = !(principal instanceof OpenIdAlfioAuthentication); + model.addAttribute("isDBAuthentication", isDBAuthentication); + if (!isDBAuthentication) { + String idpLogoutRedirectionUrl = ((OpenIdAlfioAuthentication) SecurityContextHolder.getContext().getAuthentication()).getIdpLogoutRedirectionUrl(); + model.addAttribute("idpLogoutRedirectionUrl", idpLogoutRedirectionUrl); + } else { + model.addAttribute("idpLogoutRedirectionUrl", null); + } + + Collection<String> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities() + .stream().map(GrantedAuthority::getAuthority).toList(); + + boolean isAdmin = authorities.contains(Role.ADMIN.getRoleName()); + model.addAttribute("isOwner", isAdmin || authorities.contains(Role.OWNER.getRoleName())); + model.addAttribute("isAdmin", isAdmin); + // + addCommonModelAttributes(model, request, version, environment); + model.addAttribute("displayProjectBanner", isAdmin && configurationManager.getForSystem(SHOW_PROJECT_BANNER).getValueAsBooleanOrDefault()); + // + + try (var os = response.getOutputStream()) { + response.setContentType(TEXT_HTML_CHARSET_UTF_8); + response.setCharacterEncoding(UTF_8); + var nonce = cspConfigurer.addCspHeader(response, false); + model.addAttribute(NONCE, nonce); + templateManager.renderHtml(new ClassPathResource("alfio/web-templates/admin-index.ms"), model.asMap(), os); + } + } +} diff --git a/src/main/java/alfio/controller/AuthenticationController.java b/src/main/java/alfio/controller/AuthenticationController.java new file mode 100644 index 0000000000..b1db2f4d40 --- /dev/null +++ b/src/main/java/alfio/controller/AuthenticationController.java @@ -0,0 +1,125 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller; + +import alfio.config.authentication.support.AuthenticationConstants; +import alfio.controller.support.CSPConfigurer; +import alfio.controller.support.UserStatus; +import alfio.manager.system.ConfigurationLevel; +import alfio.manager.system.ConfigurationManager; +import alfio.util.TemplateManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; +import java.util.EnumSet; + +import static alfio.controller.Constants.*; +import static alfio.model.system.ConfigurationKeys.ENABLE_CAPTCHA_FOR_LOGIN; +import static alfio.model.system.ConfigurationKeys.RECAPTCHA_API_KEY; +import static com.github.scribejava.core.model.OAuthConstants.NONCE; + +@Controller +public class AuthenticationController { + + private static final String REDIRECT_ADMIN = "redirect:/admin/"; + + private final ConfigurationManager configurationManager; + private final Environment environment; + private final CSPConfigurer cspConfigurer; + private final TemplateManager templateManager; + + public AuthenticationController(ConfigurationManager configurationManager, + Environment environment, + CSPConfigurer cspConfigurer, + TemplateManager templateManager) { + this.configurationManager = configurationManager; + this.environment = environment; + this.cspConfigurer = cspConfigurer; + this.templateManager = templateManager; + } + + @GetMapping(AuthenticationConstants.AUTHENTICATION_STATUS) + public ResponseEntity<UserStatus> authenticationStatus(Principal principal, + @Value("${alfio.version}") String version) { + + return ResponseEntity.ok(new UserStatus( + principal != null, + principal != null ? principal.getName() : null, + version, + demoModeEnabled(environment), + devModeEnabled(environment), + prodModeEnabled(environment) + )); + } + + @GetMapping("/authentication") + public void getLoginPage(@RequestParam(value="failed", required = false) String failed, + @RequestParam(value = "recaptchaFailed", required = false) String recaptchaFailed, + Model model, + Principal principal, + HttpServletRequest request, + HttpServletResponse response, + @Value("${alfio.version}") String version) throws IOException { + + if(principal != null) { + response.sendRedirect("/admin/"); + return; + } + model.addAttribute("failed", failed != null); + model.addAttribute("recaptchaFailed", recaptchaFailed != null); + model.addAttribute("hasRecaptchaApiKey", false); + + // + addCommonModelAttributes(model, request, version, environment); + model.addAttribute("request", request); + + // + + var configuration = configurationManager.getFor(EnumSet.of(RECAPTCHA_API_KEY, ENABLE_CAPTCHA_FOR_LOGIN), ConfigurationLevel.system()); + + configuration.get(RECAPTCHA_API_KEY).getValue() + .filter(key -> configuration.get(ENABLE_CAPTCHA_FOR_LOGIN).getValueAsBooleanOrDefault()) + .ifPresent(key -> { + model.addAttribute("hasRecaptchaApiKey", true); + model.addAttribute("recaptchaApiKey", key); + }); + try (var os = response.getOutputStream()) { + response.setContentType(TEXT_HTML_CHARSET_UTF_8); + response.setCharacterEncoding(UTF_8); + var nonce = cspConfigurer.addCspHeader(response, false); + model.addAttribute(NONCE, nonce); + templateManager.renderHtml(new ClassPathResource("alfio/web-templates/login.ms"), model.asMap(), os); + } + } + + @PostMapping("/authenticate") + public String doLogin() { + return REDIRECT_ADMIN; + } + +} diff --git a/src/main/java/alfio/controller/Constants.java b/src/main/java/alfio/controller/Constants.java new file mode 100644 index 0000000000..b41f6487f6 --- /dev/null +++ b/src/main/java/alfio/controller/Constants.java @@ -0,0 +1,64 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller; + +import alfio.config.Initializer; +import alfio.config.WebSecurityConfig; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.ui.Model; + +import javax.servlet.http.HttpServletRequest; + +public interface Constants { + String TEXT_HTML_CHARSET_UTF_8 = "text/html;charset=UTF-8"; + String UTF_8 = "UTF-8"; + String NONCE = "nonce"; + String REDIRECT = "redirect:"; + String EVENT_SHORT_NAME = "eventShortName"; + String NOT_FOUND = "not-found"; + String CONTENT = "content"; + String PROPERTY = "property"; + + static void addCommonModelAttributes(Model model, HttpServletRequest request, String version, Environment environment) { + var contextPath = StringUtils.appendIfMissing(request.getContextPath(), "/") + version; + model.addAttribute("contextPath", contextPath); + model.addAttribute("demoModeEnabled", demoModeEnabled(environment)); + model.addAttribute("devModeEnabled", devModeEnabled(environment)); + model.addAttribute("prodModeEnabled", prodModeEnabled(environment)); + model.addAttribute(WebSecurityConfig.CSRF_PARAM_NAME, request.getAttribute(CsrfToken.class.getName())); + } + + static boolean prodModeEnabled(Environment environment) { + return profileActive(environment, Initializer.PROFILE_LIVE); + } + + static boolean devModeEnabled(Environment environment) { + return profileActive(environment, Initializer.PROFILE_DEV); + } + + static boolean demoModeEnabled(Environment environment) { + return profileActive(environment, Initializer.PROFILE_DEMO); + } + + private static boolean profileActive(Environment environment, String profile) { + return environment.acceptsProfiles(Profiles.of(profile)); + } + +} diff --git a/src/main/java/alfio/controller/IndexController.java b/src/main/java/alfio/controller/IndexController.java index 0d90c44da3..2b6ee9e674 100644 --- a/src/main/java/alfio/controller/IndexController.java +++ b/src/main/java/alfio/controller/IndexController.java @@ -16,40 +16,32 @@ */ package alfio.controller; -import alfio.config.Initializer; -import alfio.config.WebSecurityConfig; -import alfio.config.authentication.support.OpenIdAlfioAuthentication; +import alfio.controller.api.v2.model.Language; import alfio.controller.api.v2.user.support.EventLoader; +import alfio.controller.support.CSPConfigurer; import alfio.manager.PurchaseContextManager; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.openid.OpenIdAuthenticationManager; import alfio.manager.system.ConfigurationLevel; import alfio.manager.system.ConfigurationManager; -import alfio.model.*; +import alfio.model.ContentLanguage; +import alfio.model.EventDescription; +import alfio.model.FileBlobMetadata; +import alfio.model.TicketReservationStatusAndValidation; import alfio.model.system.ConfigurationKeys; -import alfio.model.user.Role; import alfio.repository.*; import alfio.repository.user.OrganizationRepository; import alfio.util.Json; import alfio.util.MustacheCustomTag; import alfio.util.RequestUtils; -import alfio.util.TemplateManager; import ch.digitalfondue.jfiveparse.*; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.binary.Hex; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.env.Environment; -import org.springframework.core.env.Profiles; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.util.UriComponentsBuilder; @@ -61,45 +53,26 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.security.SecureRandom; import java.util.*; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import static alfio.model.system.ConfigurationKeys.*; +import static alfio.config.Initializer.PROFILE_LIVE; +import static alfio.controller.Constants.*; +import static alfio.model.system.ConfigurationKeys.BASE_CUSTOM_CSS; +import static alfio.util.HttpUtils.APPLICATION_JSON; +import static java.util.Objects.requireNonNull; @Controller -@AllArgsConstructor -@Slf4j +@Profile(PROFILE_LIVE) public class IndexController { - private static final String REDIRECT_ADMIN = "redirect:/admin/"; private static final String TEXT_HTML_CHARSET_UTF_8 = "text/html;charset=UTF-8"; private static final String UTF_8 = "UTF-8"; - - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - - private static final Document INDEX_PAGE; - private static final Document OPEN_GRAPH_PAGE; - - static { - try (var idxIs = new ClassPathResource("alfio-public-frontend-index.html").getInputStream(); - var idxOpenIs = new ClassPathResource("alfio/web-templates/event-open-graph-page.html").getInputStream(); - var idxIsR = new InputStreamReader(idxIs, StandardCharsets.UTF_8); - var idxOpenGraphReader = new InputStreamReader(idxOpenIs, StandardCharsets.UTF_8)) { - INDEX_PAGE = JFiveParse.parse(idxIsR); - OPEN_GRAPH_PAGE = JFiveParse.parse(idxOpenGraphReader); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - + private final Document indexPage; + private final Document openGraphPage; private final ConfigurationManager configurationManager; private final EventRepository eventRepository; - private final Environment environment; - private final TemplateManager templateManager; private final FileUploadRepository fileUploadRepository; private final MessageSourceManager messageSourceManager; private final EventDescriptionRepository eventDescriptionRepository; @@ -108,6 +81,46 @@ public class IndexController { private final SubscriptionRepository subscriptionRepository; private final EventLoader eventLoader; private final PurchaseContextManager purchaseContextManager; + private final Json json; + private final CsrfTokenRepository csrfTokenRepository; + private final CSPConfigurer cspConfigurer; + + public IndexController(ConfigurationManager configurationManager, + EventRepository eventRepository, + FileUploadRepository fileUploadRepository, + MessageSourceManager messageSourceManager, + EventDescriptionRepository eventDescriptionRepository, + OrganizationRepository organizationRepository, + TicketReservationRepository ticketReservationRepository, + SubscriptionRepository subscriptionRepository, + EventLoader eventLoader, + PurchaseContextManager purchaseContextManager, + CsrfTokenRepository csrfTokenRepository, + CSPConfigurer cspConfigurer, + Json json) { + this.configurationManager = configurationManager; + this.eventRepository = eventRepository; + this.fileUploadRepository = fileUploadRepository; + this.messageSourceManager = messageSourceManager; + this.eventDescriptionRepository = eventDescriptionRepository; + this.organizationRepository = organizationRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.subscriptionRepository = subscriptionRepository; + this.eventLoader = eventLoader; + this.purchaseContextManager = purchaseContextManager; + this.csrfTokenRepository = csrfTokenRepository; + this.cspConfigurer = cspConfigurer; + this.json = json; + try (var idxIs = new ClassPathResource("alfio-public-frontend-index.html").getInputStream(); + var idxOpenIs = new ClassPathResource("alfio/web-templates/event-open-graph-page.html").getInputStream(); + var idxIsR = new InputStreamReader(idxIs, StandardCharsets.UTF_8); + var idxOpenGraphReader = new InputStreamReader(idxOpenIs, StandardCharsets.UTF_8)) { + indexPage = JFiveParse.parse(idxIsR); + openGraphPage = JFiveParse.parse(idxOpenGraphReader); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } @RequestMapping(value = "/", method = RequestMethod.HEAD) @@ -193,7 +206,7 @@ public ResponseEntity<String> replyToK8s() { "/my-orders", "/my-profile", }) - public void replyToIndex(@PathVariable(value = "eventShortName", required = false) String eventShortName, + public void replyToIndex(@PathVariable(value = EVENT_SHORT_NAME, required = false) String eventShortName, @PathVariable(value = "subscriptionId", required = false) String subscriptionId, @RequestHeader(value = "User-Agent", required = false) String userAgent, @RequestParam(value = "lang", required = false) String lang, @@ -203,50 +216,55 @@ public void replyToIndex(@PathVariable(value = "eventShortName", required = fals response.setContentType(TEXT_HTML_CHARSET_UTF_8); response.setCharacterEncoding(UTF_8); - var nonce = addCspHeader(response, detectConfigurationLevel(eventShortName, subscriptionId), true); + var nonce = cspConfigurer.addCspHeader(response, detectConfigurationLevel(eventShortName, subscriptionId), true); if (eventShortName != null && RequestUtils.isSocialMediaShareUA(userAgent) && eventRepository.existsByShortName(eventShortName)) { try (var os = response.getOutputStream(); var osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { - var res = getOpenGraphPage((Document) OPEN_GRAPH_PAGE.cloneNode(true), eventShortName, request, lang); + var res = getOpenGraphPage((Document) openGraphPage.cloneNode(true), eventShortName, request, lang); JFiveParse.serialize(res, osw); } } else { try (var os = response.getOutputStream(); var osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { var baseCustomCss = configurationManager.getForSystem(BASE_CUSTOM_CSS).getValueOrNull(); - var idx = INDEX_PAGE.cloneNode(true); + var idx = indexPage.cloneNode(true); if(session.getAttribute(OpenIdAuthenticationManager.USER_SIGNED_UP) != null) { Optional.ofNullable(IterableUtils.get(idx.getElementsByTagName("html"), 0)) .ifPresent(html -> html.setAttribute("data-signed-up", "true")); session.removeAttribute(OpenIdAuthenticationManager.USER_SIGNED_UP); } - idx.getElementsByTagName("script").forEach(element -> element.setAttribute("nonce", nonce)); + idx.getElementsByTagName("script").forEach(element -> element.setAttribute(NONCE, nonce)); var head = idx.getElementsByTagName("head").get(0); - head.appendChild(buildScripTag(Json.toJson(configurationManager.getInfo(session)), "application/json", "preload-info", null)); - head.appendChild(buildScripTag(Json.toJson(messageSourceManager.getBundleAsMap("alfio.i18n.public", true, "en")), "application/json", "preload-bundle", "en")); + head.appendChild(buildScripTag(json.asJsonString(configurationManager.getInfo(session)), APPLICATION_JSON, "preload-info", null)); + var httpServletRequest = requireNonNull(request.getNativeRequest(HttpServletRequest.class)); + head.appendChild(buildMetaTag("GID", request.getSessionId())); + var csrf = csrfTokenRepository.loadToken(httpServletRequest); + if (csrf == null) { + csrf = csrfTokenRepository.generateToken(httpServletRequest); + } + head.appendChild(buildMetaTag("XSRF_TOKEN", csrf.getToken())); if (baseCustomCss != null) { var style = new Element("style"); style.setAttribute("type", "text/css"); style.appendChild(new Text(baseCustomCss)); head.appendChild(style); } - if (eventShortName != null) { - eventLoader.loadEventInfo(eventShortName, session).ifPresent(ev -> { - head.appendChild(buildScripTag(Json.toJson(ev), "application/json", "preload-event", eventShortName)); - }); - } + preloadTranslations(eventShortName, request, session, eventLoader, head, messageSourceManager, idx, json, lang); JFiveParse.serialize(idx, osw); } } } @GetMapping("/event/{eventShortName}/reservation/{reservationId}") - public String redirectEventToReservation(@PathVariable(value = "eventShortName") String eventShortName, @PathVariable(value = "reservationId") String reservationId) { + public String redirectEventToReservation(@PathVariable(value = EVENT_SHORT_NAME) String eventShortName, + @PathVariable(value = "reservationId") String reservationId, + @RequestParam(value = "subscription", required = false) String subscriptionId) { if (eventRepository.existsByShortName(eventShortName)) { var reservationStatusUrlSegment = ticketReservationRepository.findOptionalStatusAndValidationById(reservationId) - .map(IndexController::reservationStatusToUrlMapping).orElse("not-found"); - - return "redirect:" + UriComponentsBuilder.fromPath("/event/{eventShortName}/reservation/{reservationId}/{status}") - .buildAndExpand(Map.of("eventShortName", eventShortName, "reservationId", reservationId, "status",reservationStatusUrlSegment)) + .map(IndexController::reservationStatusToUrlMapping).orElse(NOT_FOUND); + return REDIRECT + UriComponentsBuilder.fromPath("/event/{eventShortName}/reservation/{reservationId}/{status}") + // if subscription param is present, we forward it to the reservation resource + .queryParamIfPresent("subscription", Optional.ofNullable(StringUtils.trimToNull(subscriptionId))) + .buildAndExpand(Map.of(EVENT_SHORT_NAME, eventShortName, "reservationId", reservationId, "status",reservationStatusUrlSegment)) .toUriString(); } else { return "redirect:/"; @@ -257,9 +275,9 @@ public String redirectEventToReservation(@PathVariable(value = "eventShortName") public String redirectSubscriptionToReservation(@PathVariable("subscriptionId") String subscriptionId, @PathVariable("reservationId") String reservationId) { if (subscriptionRepository.existsById(UUID.fromString(subscriptionId))) { var reservationStatusUrlSegment = ticketReservationRepository.findOptionalStatusAndValidationById(reservationId) - .map(IndexController::reservationStatusToUrlMapping).orElse("not-found"); + .map(IndexController::reservationStatusToUrlMapping).orElse(NOT_FOUND); - return "redirect:" + UriComponentsBuilder.fromPath("/subscription/{subscriptionId}/reservation/{reservationId}/{status}") + return REDIRECT + UriComponentsBuilder.fromPath("/subscription/{subscriptionId}/reservation/{reservationId}/{status}") .buildAndExpand(Map.of("subscriptionId", subscriptionId, "reservationId", reservationId, "status",reservationStatusUrlSegment)) .toUriString(); } else { @@ -267,6 +285,33 @@ public String redirectSubscriptionToReservation(@PathVariable("subscriptionId") } } + static void preloadTranslations(String eventShortName, + ServletWebRequest request, + HttpSession session, + EventLoader eventLoader, + Element head, + MessageSourceManager messageSourceManager, + Node idx, + Json json, + String lang) { + String preloadLang = Objects.requireNonNullElse(lang, "en"); + if (eventShortName != null) { + var eventInfoOptional = eventLoader.loadEventInfo(eventShortName, session); + if (eventInfoOptional.isPresent()) { + var ev = eventInfoOptional.get(); + head.appendChild(buildScripTag(json.asJsonString(ev), APPLICATION_JSON, "preload-event", eventShortName)); + preloadLang = getMatchingLocale(request, ev.getContentLanguages().stream().map(Language::getLocale).toList(), lang).getLanguage(); + } + } + head.appendChild(buildScripTag(json.asJsonString(messageSourceManager.getBundleAsMap("alfio.i18n.public", true, preloadLang, MessageSourceManager.PUBLIC_FRONTEND)), "application/json", "preload-bundle", preloadLang)); + // add fallback in english + if (!"en".equals(preloadLang)) { + head.appendChild(buildScripTag(json.asJsonString(messageSourceManager.getBundleAsMap("alfio.i18n.public", true, "en", MessageSourceManager.PUBLIC_FRONTEND)), "application/json", "preload-bundle", "en")); + } + var htmlElement = IterableUtils.get(idx.getElementsByTagName("html"), 0); + htmlElement.setAttribute("lang", preloadLang); + } + private static Element buildScripTag(String content, String type, String id, String param) { var e = new Element("script"); e.appendChild(new Text(content)); @@ -279,27 +324,39 @@ private static Element buildScripTag(String content, String type, String id, Str } private static String reservationStatusToUrlMapping(TicketReservationStatusAndValidation status) { - switch (status.getStatus()) { - case PENDING: return Boolean.TRUE.equals(status.getValidated()) ? "overview" : "book"; - case COMPLETE: return "success"; - case OFFLINE_PAYMENT: return "waiting-payment"; - case DEFERRED_OFFLINE_PAYMENT: return "deferred-payment"; - case EXTERNAL_PROCESSING_PAYMENT: - case WAITING_EXTERNAL_CONFIRMATION: return "processing-payment"; - case IN_PAYMENT: - case STUCK: return "error"; - default: return "not-found"; // <- this may be a little bit aggressive + return switch (status.getStatus()) { + case PENDING -> Boolean.TRUE.equals(status.getValidated()) ? "overview" : "book"; + case COMPLETE, FINALIZING -> "success"; + case OFFLINE_PAYMENT, OFFLINE_FINALIZING -> "waiting-payment"; + case DEFERRED_OFFLINE_PAYMENT -> "deferred-payment"; + case EXTERNAL_PROCESSING_PAYMENT, WAITING_EXTERNAL_CONFIRMATION -> "processing-payment"; + case IN_PAYMENT, STUCK -> "error"; + default -> NOT_FOUND; // <- this may be a little bit aggressive + }; + } + + + /** + * Return the best matching locale. + * + * @param request + * @param contextLanguages list of languages configured for the event (o other contexts) + * @param lang override passed as parameter + * @return + */ + private static Locale getMatchingLocale(ServletWebRequest request, List<String> contextLanguages, String lang) { + var locale = RequestUtils.getMatchingLocale(request, contextLanguages); + if (lang != null && contextLanguages.stream().anyMatch(lang::equalsIgnoreCase)) { + locale = Locale.forLanguageTag(lang); } + return locale; } // see https://github.com/alfio-event/alf.io/issues/708 // use ngrok to test the preview private Document getOpenGraphPage(Document eventOpenGraph, String eventShortName, ServletWebRequest request, String lang) { var event = eventRepository.findByShortName(eventShortName); - var locale = RequestUtils.getMatchingLocale(request, event); - if (lang != null && event.getContentLanguages().stream().map(ContentLanguage::getLanguage).anyMatch(lang::equalsIgnoreCase)) { - locale = Locale.forLanguageTag(lang); - } + var locale = getMatchingLocale(request, event.getContentLanguages().stream().map(ContentLanguage::getLanguage).toList(), lang); var baseUrl = configurationManager.getForSystem(ConfigurationKeys.BASE_URL).getRequiredValue(); @@ -311,37 +368,45 @@ private Document getOpenGraphPage(Document eventOpenGraph, String eventShortName // - getMetaElement(eventOpenGraph, "name", "twitter:image").setAttribute("content", baseUrl + "/file/" + event.getFileBlobId()); + getMetaElement(eventOpenGraph, "name", "twitter:image").setAttribute(CONTENT, baseUrl + "/file/" + event.getFileBlobId()); // eventOpenGraph.getElementsByTagName("title").get(0).appendChild(new Text(title)); - getMetaElement(eventOpenGraph, "property", "og:title").setAttribute("content", title); - getMetaElement(eventOpenGraph, "property","og:image").setAttribute("content", baseUrl + "/file/" + event.getFileBlobId()); + getMetaElement(eventOpenGraph, PROPERTY, "og:title").setAttribute(CONTENT, title); + getMetaElement(eventOpenGraph, PROPERTY,"og:image").setAttribute(CONTENT, baseUrl + "/file/" + event.getFileBlobId()); var eventDesc = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(event.getId(), EventDescription.EventDescriptionType.DESCRIPTION, locale.toLanguageTag()).orElse("").trim(); var firstLine = Pattern.compile("\n").splitAsStream(MustacheCustomTag.renderToTextCommonmark(eventDesc)).findFirst().orElse(""); - getMetaElement(eventOpenGraph, "property","og:description").setAttribute("content", firstLine); + getMetaElement(eventOpenGraph, PROPERTY,"og:description").setAttribute(CONTENT, firstLine); var org = organizationRepository.getById(event.getOrganizationId()); var author = String.format("%s <%s>", org.getName(), org.getEmail()); - getMetaElement(eventOpenGraph, "name", "author").setAttribute("content", author); + getMetaElement(eventOpenGraph, "name", "author").setAttribute(CONTENT, author); fileUploadRepository.findById(event.getFileBlobId()).ifPresent(metadata -> { var attributes = metadata.getAttributes(); if (attributes.containsKey(FileBlobMetadata.ATTR_IMG_HEIGHT) && attributes.containsKey(FileBlobMetadata.ATTR_IMG_WIDTH)) { - head.appendChild(buildMetaTag("og:image:width", attributes.get(FileBlobMetadata.ATTR_IMG_WIDTH))); - head.appendChild(buildMetaTag("og:image:height", attributes.get(FileBlobMetadata.ATTR_IMG_HEIGHT))); + head.appendChild(buildOGMetaTag("og:image:width", attributes.get(FileBlobMetadata.ATTR_IMG_WIDTH))); + head.appendChild(buildOGMetaTag("og:image:height", attributes.get(FileBlobMetadata.ATTR_IMG_HEIGHT))); } }); return eventOpenGraph; } - private static Element buildMetaTag(String propertyValue, String contentValue) { + private static Element buildOGMetaTag(String propertyValue, String contentValue) { + return buildMetaTag(PROPERTY, propertyValue, contentValue); + } + + private static Element buildMetaTag(String name, String content) { + return buildMetaTag("name", name, content); + } + + private static Element buildMetaTag(String property, String propertyValue, String content) { var meta = new Element("meta"); - meta.setAttribute("property", propertyValue); - meta.setAttribute("content", contentValue); + meta.setAttribute(property, propertyValue); + meta.setAttribute(CONTENT, content); return meta; } @@ -352,155 +417,15 @@ private static Element getMetaElement(Document document, String attrName, String @GetMapping(value = { "/event/{eventShortName}/code/{code}", "/e/{eventShortName}/c/{code}"}) - public String redirectCode(@PathVariable("eventShortName") String eventName, + public String redirectCode(@PathVariable(EVENT_SHORT_NAME) String eventName, @PathVariable("code") String code) { - return "redirect:" + UriComponentsBuilder.fromPath("/api/v2/public/event/{eventShortName}/code/{code}") - .build(Map.of("eventShortName", eventName, "code", code)); + return REDIRECT + UriComponentsBuilder.fromPath("/api/v2/public/event/{eventShortName}/code/{code}") + .build(Map.of(EVENT_SHORT_NAME, eventName, "code", code)); } @GetMapping("/e/{eventShortName}") - public String redirectEvent(@PathVariable("eventShortName") String eventName) { - return "redirect:" + UriComponentsBuilder.fromPath("/event/{eventShortName}").build(Map.of("eventShortName", eventName)); - } - - // login related - @GetMapping("/authentication") - public void getLoginPage(@RequestParam(value="failed", required = false) String failed, - @RequestParam(value = "recaptchaFailed", required = false) String recaptchaFailed, - Model model, - Principal principal, - HttpServletRequest request, - HttpServletResponse response, - @Value("${alfio.version}") String version) throws IOException { - - if(principal != null) { - response.sendRedirect("/admin/"); - return; - } - model.addAttribute("failed", failed != null); - model.addAttribute("recaptchaFailed", recaptchaFailed != null); - model.addAttribute("hasRecaptchaApiKey", false); - - // - addCommonModelAttributes(model, request, version); - model.addAttribute("request", request); - - // - - var configuration = configurationManager.getFor(EnumSet.of(RECAPTCHA_API_KEY, ENABLE_CAPTCHA_FOR_LOGIN), ConfigurationLevel.system()); - - configuration.get(RECAPTCHA_API_KEY).getValue() - .filter(key -> configuration.get(ENABLE_CAPTCHA_FOR_LOGIN).getValueAsBooleanOrDefault()) - .ifPresent(key -> { - model.addAttribute("hasRecaptchaApiKey", true); - model.addAttribute("recaptchaApiKey", key); - }); - try (var os = response.getOutputStream()) { - response.setContentType(TEXT_HTML_CHARSET_UTF_8); - response.setCharacterEncoding(UTF_8); - var nonce = addCspHeader(response, false); - model.addAttribute("nonce", nonce); - templateManager.renderHtml(new ClassPathResource("alfio/web-templates/login.ms"), model.asMap(), os); - } - } - - @PostMapping("/authenticate") - public String doLogin() { - return REDIRECT_ADMIN; - } - // - - - // admin index - @GetMapping("/admin") - public void adminHome(Model model, @Value("${alfio.version}") String version, HttpServletRequest request, HttpServletResponse response, Principal principal) throws IOException { - model.addAttribute("alfioVersion", version); - model.addAttribute("username", principal.getName()); - model.addAttribute("basicConfigurationNeeded", configurationManager.isBasicConfigurationNeeded()); - - boolean isDBAuthentication = !(principal instanceof OpenIdAlfioAuthentication); - model.addAttribute("isDBAuthentication", isDBAuthentication); - if (!isDBAuthentication) { - String idpLogoutRedirectionUrl = ((OpenIdAlfioAuthentication) SecurityContextHolder.getContext().getAuthentication()).getIdpLogoutRedirectionUrl(); - model.addAttribute("idpLogoutRedirectionUrl", idpLogoutRedirectionUrl); - } else { - model.addAttribute("idpLogoutRedirectionUrl", null); - } - - Collection<String> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities() - .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); - - boolean isAdmin = authorities.contains(Role.ADMIN.getRoleName()); - model.addAttribute("isOwner", isAdmin || authorities.contains(Role.OWNER.getRoleName())); - model.addAttribute("isAdmin", isAdmin); - // - addCommonModelAttributes(model, request, version); - model.addAttribute("displayProjectBanner", isAdmin && configurationManager.getForSystem(SHOW_PROJECT_BANNER).getValueAsBooleanOrDefault()); - // - - try (var os = response.getOutputStream()) { - response.setContentType(TEXT_HTML_CHARSET_UTF_8); - response.setCharacterEncoding(UTF_8); - var nonce = addCspHeader(response, false); - model.addAttribute("nonce", nonce); - templateManager.renderHtml(new ClassPathResource("alfio/web-templates/admin-index.ms"), model.asMap(), os); - } - } - - private void addCommonModelAttributes(Model model, HttpServletRequest request, String version) { - var contextPath = StringUtils.appendIfMissing(request.getContextPath(), "/") + version; - model.addAttribute("contextPath", contextPath); - model.addAttribute("demoModeEnabled", environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_DEMO))); - model.addAttribute("devModeEnabled", environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_DEV))); - model.addAttribute("prodModeEnabled", environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_LIVE))); - model.addAttribute(WebSecurityConfig.CSRF_PARAM_NAME, request.getAttribute(CsrfToken.class.getName())); - } - - - private static String getNonce() { - var nonce = new byte[16]; //128 bit = 16 bytes - SECURE_RANDOM.nextBytes(nonce); - return Hex.encodeHexString(nonce); - } - - public String addCspHeader(HttpServletResponse response, boolean embeddingSupported) { - return addCspHeader(response, ConfigurationLevel.system(), embeddingSupported); - } - - public String addCspHeader(HttpServletResponse response, ConfigurationLevel configurationLevel, boolean embeddingSupported) { - - var nonce = getNonce(); - - String reportUri = ""; - - var conf = configurationManager.getFor(List.of(SECURITY_CSP_REPORT_ENABLED, SECURITY_CSP_REPORT_URI, EMBED_ALLOWED_ORIGINS), configurationLevel); - - boolean enabledReport = conf.get(SECURITY_CSP_REPORT_ENABLED).getValueAsBooleanOrDefault(); - if (enabledReport) { - reportUri = " report-uri " + conf.get(SECURITY_CSP_REPORT_URI).getValueOrDefault("/report-csp-violation"); - } - // - // https://csp.withgoogle.com/docs/strict-csp.html - // with base-uri set to 'self' - - var frameAncestors = "'none'"; - var allowedContainer = conf.get(EMBED_ALLOWED_ORIGINS).getValueOrNull(); - if (embeddingSupported && StringUtils.isNotBlank(allowedContainer)) { - var splitHosts = allowedContainer.split("[,\n]"); - frameAncestors = String.join(" ", splitHosts); - // IE11 - response.addHeader("X-Frame-Options", "ALLOW-FROM "+splitHosts[0]); - } else { - response.addHeader("X-Frame-Options", "DENY"); - } - - response.addHeader("Content-Security-Policy", "object-src 'none'; "+ - "script-src 'strict-dynamic' 'nonce-" + nonce + "' 'unsafe-inline' http: https:; " + - "base-uri 'self'; " + - "frame-ancestors " + frameAncestors + "; " - + reportUri); - - return nonce; + public String redirectEvent(@PathVariable(EVENT_SHORT_NAME) String eventName) { + return REDIRECT + UriComponentsBuilder.fromPath("/event/{eventShortName}").build(Map.of(EVENT_SHORT_NAME, eventName)); } private ConfigurationLevel detectConfigurationLevel(String eventShortName, String subscriptionId) { diff --git a/src/main/java/alfio/controller/OnlineCheckInController.java b/src/main/java/alfio/controller/OnlineCheckInController.java index 8bb9cac92e..93408c2120 100644 --- a/src/main/java/alfio/controller/OnlineCheckInController.java +++ b/src/main/java/alfio/controller/OnlineCheckInController.java @@ -19,9 +19,9 @@ import alfio.manager.CheckInManager; import alfio.manager.ExtensionManager; import alfio.manager.TicketReservationManager; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,14 +35,20 @@ import static alfio.util.EventUtil.findMatchingLink; @Controller -@AllArgsConstructor -@Log4j2 public class OnlineCheckInController { + private static final Logger log = LoggerFactory.getLogger(OnlineCheckInController.class); + private final TicketReservationManager ticketReservationManager; private final CheckInManager checkInManager; private final ExtensionManager extensionManager; + public OnlineCheckInController(TicketReservationManager ticketReservationManager, CheckInManager checkInManager, ExtensionManager extensionManager) { + this.ticketReservationManager = ticketReservationManager; + this.checkInManager = checkInManager; + this.extensionManager = extensionManager; + } + @GetMapping("/event/{shortName}/ticket/{ticketUUID}/check-in/{ticketCodeHash}") public String performCheckIn(@PathVariable("shortName") String eventShortName, @PathVariable("ticketUUID") String ticketUUID, @@ -52,7 +58,7 @@ public String performCheckIn(@PathVariable("shortName") String eventShortName, .flatMap(data -> { var ticket = data.getTicket(); var event = data.getEventWithCheckInInfo(); - String ticketCode = ticket.ticketCode(event.getPrivateKey()); + String ticketCode = ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()); if(MessageDigest.isEqual(DigestUtils.sha256Hex(ticketCode).getBytes(StandardCharsets.UTF_8), ticketCodeHash.getBytes(StandardCharsets.UTF_8))) { log.debug("code successfully validated for ticket {}", ticketUUID); // check-in can be done. Let's check if there is a redirection URL diff --git a/src/main/java/alfio/controller/api/ApiControllerExceptionHandler.java b/src/main/java/alfio/controller/api/ApiControllerExceptionHandler.java index 0ac1cf1933..c51c25e081 100644 --- a/src/main/java/alfio/controller/api/ApiControllerExceptionHandler.java +++ b/src/main/java/alfio/controller/api/ApiControllerExceptionHandler.java @@ -17,7 +17,8 @@ package alfio.controller.api; import alfio.controller.api.v2.user.support.ReservationAccessDenied; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -33,9 +34,10 @@ "alfio.controller.api.support", "alfio.controller.api.v1", "alfio.controller.api.v2"}) -@Log4j2 public class ApiControllerExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(ApiControllerExceptionHandler.class); + @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody diff --git a/src/main/java/alfio/controller/api/admin/AdditionalServiceApiController.java b/src/main/java/alfio/controller/api/admin/AdditionalServiceApiController.java index c3759ee407..9db019422c 100644 --- a/src/main/java/alfio/controller/api/admin/AdditionalServiceApiController.java +++ b/src/main/java/alfio/controller/api/admin/AdditionalServiceApiController.java @@ -19,6 +19,7 @@ import alfio.manager.AdditionalServiceManager; import alfio.manager.EventManager; import alfio.model.AdditionalService; +import alfio.model.AdditionalServiceItem; import alfio.model.Event; import alfio.model.PriceContainer; import alfio.model.modification.EventModification; @@ -27,9 +28,9 @@ import alfio.util.ExportUtils; import alfio.util.MonetaryUtil; import alfio.util.Validator; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; @@ -52,14 +53,22 @@ @RestController @RequestMapping("/admin/api") -@RequiredArgsConstructor -@Log4j2 public class AdditionalServiceApiController { + private static final Logger log = LoggerFactory.getLogger(AdditionalServiceApiController.class); + private final EventManager eventManager; private final EventRepository eventRepository; private final AdditionalServiceManager additionalServiceManager; + public AdditionalServiceApiController(EventManager eventManager, + EventRepository eventRepository, + AdditionalServiceManager additionalServiceManager) { + this.eventManager = eventManager; + this.eventRepository = eventRepository; + this.additionalServiceManager = additionalServiceManager; + } + @ExceptionHandler({IllegalArgumentException.class}) public ResponseEntity<String> handleBadRequest(Exception e) { @@ -87,7 +96,7 @@ public List<EventModification.AdditionalService> loadAll(@PathVariable("eventId" } @GetMapping("/event/{eventId}/additional-services/count") - public Map<Integer, Integer> countUse(@PathVariable("eventId") int eventId) { + public Map<Integer, Map<AdditionalServiceItem.AdditionalServiceItemStatus, Integer>> countUse(@PathVariable("eventId") int eventId) { return additionalServiceManager.countUsageForEvent(eventId); } @@ -155,6 +164,7 @@ public void exportAdditionalServices(@PathVariable("eventName") String eventName "Name", "Creation", "Last Update", + "Status", "Reservation ID", "Reservation First name", "Reservation Last name", @@ -171,6 +181,7 @@ public void exportAdditionalServices(@PathVariable("eventName") String eventName item.getAdditionalServiceTitle(), item.getUtcCreation().withZoneSameInstant(event.getZoneId()).format(formatter), requireNonNullElse(item.getUtcLastModified(), item.getUtcCreation()).withZoneSameInstant(event.getZoneId()).format(formatter), + item.getAdditionalServiceItemStatus().name(), item.getTicketsReservationUuid(), item.getFirstName(), item.getLastName(), diff --git a/src/main/java/alfio/controller/api/admin/AdminPaymentsApiController.java b/src/main/java/alfio/controller/api/admin/AdminPaymentsApiController.java new file mode 100644 index 0000000000..7350e7eb62 --- /dev/null +++ b/src/main/java/alfio/controller/api/admin/AdminPaymentsApiController.java @@ -0,0 +1,144 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.admin; + +import alfio.controller.api.support.PageAndContent; +import alfio.manager.PaymentManager; +import alfio.manager.PurchaseContextManager; +import alfio.manager.PurchaseContextSearchManager; +import alfio.manager.system.ConfigurationManager; +import alfio.model.PurchaseContext; +import alfio.model.ReservationPaymentDetail; +import alfio.model.modification.TransactionMetadataModification; +import alfio.model.system.ConfigurationKeys; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import static alfio.util.ExportUtils.exportExcel; +import static java.util.Objects.requireNonNullElse; +import static org.apache.commons.lang3.StringUtils.trimToNull; + +@RestController +@RequestMapping("/admin/api/payments") +public class AdminPaymentsApiController { + + private static final String[] EXPORT_COLUMNS = new String[] { + "ID", + "Name", + "Email", + "Type", + "Amount", + "Currency", + "Payment Date/Time", + "Notes" + }; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private final PurchaseContextSearchManager purchaseContextSearchManager; + private final PurchaseContextManager purchaseContextManager; + private final PaymentManager paymentManager; + private final ConfigurationManager configurationManager; + + public AdminPaymentsApiController(PurchaseContextSearchManager purchaseContextSearchManager, + PurchaseContextManager purchaseContextManager, + PaymentManager paymentManager, + ConfigurationManager configurationManager) { + this.purchaseContextSearchManager = purchaseContextSearchManager; + this.purchaseContextManager = purchaseContextManager; + this.paymentManager = paymentManager; + this.configurationManager = configurationManager; + } + + @GetMapping("/{purchaseContextType}/{publicIdentifier}/list") + PageAndContent<List<ReservationPaymentDetail>> getPaymentsForPurchaseContext(@PathVariable("purchaseContextType") PurchaseContext.PurchaseContextType purchaseContextType, + @PathVariable("publicIdentifier") String publicIdentifier, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "search", required = false) String search, + Principal principal) { + return purchaseContextManager.findBy(purchaseContextType, publicIdentifier) + .filter(purchaseContext -> purchaseContextManager.validateAccess(purchaseContext, principal)) + .map(purchaseContext -> { + var res = purchaseContextSearchManager.findAllPaymentsFor(purchaseContext, page, search); + return new PageAndContent<>(res.getLeft(), res.getRight()); + }).orElseGet(() -> new PageAndContent<>(List.of(), 0)); + } + + @PutMapping("/{purchaseContextType}/{publicIdentifier}/reservation/{reservationId}") + ResponseEntity<String> updateTransactionData(@PathVariable("purchaseContextType") PurchaseContext.PurchaseContextType purchaseContextType, + @PathVariable("publicIdentifier") String publicIdentifier, + @PathVariable("reservationId") String reservationId, + @RequestBody TransactionMetadataModification transactionMetadataModification, + Principal principal) { + try { + return ResponseEntity.of(purchaseContextManager.findBy(purchaseContextType, publicIdentifier) + .filter(purchaseContext -> purchaseContextManager.validateAccess(purchaseContext, principal)) + .map(purchaseContext -> { + var timestampModification = transactionMetadataModification.getTimestamp(); + var timestamp = timestampModification != null ? timestampModification.toZonedDateTime(purchaseContext.getZoneId()) : null; + paymentManager.updateTransactionDetails(reservationId, transactionMetadataModification.getNotes(), timestamp, principal); + return "OK"; + })); + } catch (IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage()); + } + } + + @GetMapping("/{purchaseContextType}/{publicIdentifier}/download") + void exportPayments(@PathVariable("purchaseContextType") PurchaseContext.PurchaseContextType purchaseContextType, + @PathVariable("publicIdentifier") String publicIdentifier, + @RequestParam(value = "search", required = false) String search, + Principal principal, + HttpServletResponse response) throws IOException { + var purchaseContextOptional = purchaseContextManager.findBy(purchaseContextType, publicIdentifier); + if (purchaseContextOptional.isPresent() && purchaseContextManager.validateAccess(purchaseContextOptional.get(), principal)) { + var purchaseContext = purchaseContextOptional.get(); + boolean useInvoiceNumber = configurationManager.getFor(ConfigurationKeys.USE_INVOICE_NUMBER_AS_ID, purchaseContext.getConfigurationLevel()) + .getValueAsBooleanOrDefault(); + var data = purchaseContextSearchManager.findAllPaymentsForExport(purchaseContext, search) + .stream() + .map(d -> new String[] { + useInvoiceNumber ? requireNonNullElse(trimToNull(d.getInvoiceNumber()), "N/A") : d.getId(), + d.getFirstName() + " " + d.getLastName(), + d.getEmail(), + d.getPaymentMethod(), + d.getPaidAmount(), + d.getCurrencyCode(), + formatTimestamp(purchaseContext, d.getTransactionTimestamp()), + d.getTransactionNotes() + }); + exportExcel(purchaseContext.getDisplayName()+" payments.xlsx", "Payments", EXPORT_COLUMNS, data, response); + } else { + response.setContentType("text/plain"); + response.setStatus(HttpStatus.PRECONDITION_REQUIRED.value()); + response.getWriter().write("No payments found"); + } + } + + private static String formatTimestamp(PurchaseContext purchaseContext, String timestamp) { + return ZonedDateTime.parse(timestamp) + .withZoneSameInstant(purchaseContext.getZoneId()) + .format(DATE_TIME_FORMATTER); + } +} diff --git a/src/main/java/alfio/controller/api/admin/AdminReservationApiController.java b/src/main/java/alfio/controller/api/admin/AdminReservationApiController.java index 798f3e3eef..f9414a7c63 100644 --- a/src/main/java/alfio/controller/api/admin/AdminReservationApiController.java +++ b/src/main/java/alfio/controller/api/admin/AdminReservationApiController.java @@ -16,6 +16,8 @@ */ package alfio.controller.api.admin; +import alfio.controller.api.support.BookingInfoTicket; +import alfio.controller.api.support.BookingInfoTicketLoader; import alfio.controller.api.support.PageAndContent; import alfio.manager.*; import alfio.model.*; @@ -24,9 +26,9 @@ import alfio.model.result.ErrorCode; import alfio.model.result.Result; import alfio.model.subscription.SubscriptionWithUsageDetails; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.springframework.http.ResponseEntity; @@ -44,7 +46,6 @@ @RequestMapping("/admin/api/reservation") @RestController -@AllArgsConstructor public class AdminReservationApiController { private final AdminReservationManager adminReservationManager; @@ -52,6 +53,21 @@ public class AdminReservationApiController { private final PurchaseContextManager purchaseContextManager; private final PurchaseContextSearchManager purchaseContextSearchManager; private final TicketReservationManager ticketReservationManager; + private final BookingInfoTicketLoader bookingInfoTicketLoader; + + public AdminReservationApiController(AdminReservationManager adminReservationManager, + EventManager eventManager, + PurchaseContextManager purchaseContextManager, + PurchaseContextSearchManager purchaseContextSearchManager, + TicketReservationManager ticketReservationManager, + BookingInfoTicketLoader bookingInfoTicketLoader) { + this.adminReservationManager = adminReservationManager; + this.eventManager = eventManager; + this.purchaseContextManager = purchaseContextManager; + this.purchaseContextSearchManager = purchaseContextSearchManager; + this.ticketReservationManager = ticketReservationManager; + this.bookingInfoTicketLoader = bookingInfoTicketLoader; + } @PostMapping("/{purchaseContextType}/{publicIdentifier}/new") public Result<String> createNew(@PathVariable("purchaseContextType") PurchaseContextType purchaseContextType, @PathVariable("publicIdentifier") String publicIdentifier, @RequestBody AdminReservationModification reservation, Principal principal) { @@ -190,9 +206,21 @@ public Result<Ticket> loadTicket(@PathVariable("purchaseContextType") PurchaseCo ); } + @GetMapping("/{purchaseContextType}/{publicIdentifier}/{reservationId}/tickets-with-additional-data") + public List<Integer> ticketsWithAdditionalData(@PathVariable("purchaseContextType") PurchaseContextType purchaseContextType, + @PathVariable("publicIdentifier") String publicIdentifier, + @PathVariable("reservationId") String reservationId) { + + if(purchaseContextType != PurchaseContextType.event) { + return List.of(); + } + + return adminReservationManager.getTicketIdsWithAdditionalData(purchaseContextType, publicIdentifier, reservationId); + } + @PostMapping("/event/{publicIdentifier}/{reservationId}/remove-tickets") - public Result<Boolean> removeTickets(@PathVariable("publicIdentifier") String publicIdentifier, + public Result<RemoveResult> removeTickets(@PathVariable("publicIdentifier") String publicIdentifier, @PathVariable("reservationId") String reservationId, @RequestBody RemoveTicketsModification toRemove, Principal principal) { @@ -202,8 +230,14 @@ public Result<Boolean> removeTickets(@PathVariable("publicIdentifier") String pu .map(Map.Entry::getKey) .collect(Collectors.toList()); - adminReservationManager.removeTickets(publicIdentifier, reservationId, toRemove.getTicketIds(), toRefund, toRemove.getNotify(), toRemove.getIssueCreditNote(), principal.getName()); - return Result.success(true); + boolean issueCreditNote = adminReservationManager.removeTickets(publicIdentifier, + reservationId, + toRemove.getTicketIds(), + toRefund, + toRemove.getNotify(), + toRemove.issueCreditNote, + principal.getName()).getData(); + return Result.success(new RemoveResult(true, issueCreditNote)); } @GetMapping("/{purchaseContextType}/{publicIdentifier}/{reservationId}/payment-info") @@ -244,6 +278,21 @@ public Result<List<LightweightMailMessage>> getEmailList(@PathVariable("purchase return adminReservationManager.getEmailsForReservation(purchaseContextType, publicIdentifier, reservationId, principal.getName()); } + @GetMapping("/{purchaseContextType}/{publicIdentifier}/{reservationId}/ticket/{ticketId}/full-data") + public ResponseEntity<BookingInfoTicket> loadFullTicketData(@PathVariable("purchaseContextType") PurchaseContextType purchaseContextType, + @PathVariable("publicIdentifier") String publicIdentifier, + @PathVariable("reservationId") String reservationId, + @PathVariable("ticketId") String ticketUUID) { + if(purchaseContextType != PurchaseContextType.event) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.of( + adminReservationManager.loadFullTicketInfo(reservationId, publicIdentifier, ticketUUID) + .map(eventAndTicket -> bookingInfoTicketLoader.toBookingInfoTicket(eventAndTicket.getRight(), eventAndTicket.getLeft())) + ); + } + private TicketReservationDescriptor toReservationDescriptor(String reservationId, Triple<TicketReservation, List<Ticket>, PurchaseContext> triple) { List<SerializablePair<TicketCategory, List<Ticket>>> tickets = triple.getMiddle().stream().collect(Collectors.groupingBy(Ticket::getCategoryId)).entrySet().stream() .map(entry -> SerializablePair.of(eventManager.getTicketCategoryById(entry.getKey(), triple.getRight().event().orElseThrow().getId()), entry.getValue())) @@ -264,7 +313,6 @@ private SubscriptionWithUsageDetails buildSubscriptionDetails(PurchaseContext pu return null; } - @RequiredArgsConstructor @Getter public static class TicketReservationDescriptor { private final TicketReservation reservation; @@ -272,16 +320,39 @@ public static class TicketReservationDescriptor { private final OrderSummary orderSummary; private final List<SerializablePair<TicketCategory, List<Ticket>>> ticketsByCategory; private final SubscriptionWithUsageDetails subscriptionDetails; + + public TicketReservationDescriptor(TicketReservation reservation, + TicketReservationAdditionalInfo additionalInfo, + OrderSummary orderSummary, + List<SerializablePair<TicketCategory, List<Ticket>>> ticketsByCategory, + SubscriptionWithUsageDetails subscriptionDetails) { + this.reservation = reservation; + this.additionalInfo = additionalInfo; + this.orderSummary = orderSummary; + this.ticketsByCategory = ticketsByCategory; + this.subscriptionDetails = subscriptionDetails; + } } - @RequiredArgsConstructor + @Getter public static class RemoveTicketsModification { private final List<Integer> ticketIds; - private Map<Integer, Boolean> refundTo; + private final Map<Integer, Boolean> refundTo; private final Boolean notify; private final Boolean issueCreditNote; + @JsonCreator + public RemoveTicketsModification(@JsonProperty("ticketIds") List<Integer> ticketIds, + @JsonProperty("refundTo") Map<Integer, Boolean> refundTo, + @JsonProperty("notify") Boolean notify, + @JsonProperty("issueCreditNote") Boolean issueCreditNote) { + this.ticketIds = ticketIds; + this.refundTo = refundTo; + this.notify = notify; + this.issueCreditNote = issueCreditNote; + } + public Boolean getNotify() { return Boolean.TRUE.equals(notify); } @@ -291,9 +362,32 @@ public Boolean getIssueCreditNote() { } } - @RequiredArgsConstructor @Getter public static class RefundAmount { private final String amount; + + @JsonCreator + public RefundAmount(@JsonProperty("amount") String amount) { + this.amount = amount; + } + } + + public static class RemoveResult { + + private final boolean success; + private final boolean creditNoteGenerated; + + public RemoveResult(boolean success, boolean creditNoteGenerated) { + this.success = success; + this.creditNoteGenerated = creditNoteGenerated; + } + + public boolean isSuccess() { + return success; + } + + public boolean isCreditNoteGenerated() { + return creditNoteGenerated; + } } } diff --git a/src/main/java/alfio/controller/api/admin/AdminWaitingQueueApiController.java b/src/main/java/alfio/controller/api/admin/AdminWaitingQueueApiController.java index f5fca6044d..e8a31db344 100644 --- a/src/main/java/alfio/controller/api/admin/AdminWaitingQueueApiController.java +++ b/src/main/java/alfio/controller/api/admin/AdminWaitingQueueApiController.java @@ -29,8 +29,6 @@ import alfio.util.ClockProvider; import alfio.util.EventUtil; import alfio.util.ExportUtils; -import lombok.AllArgsConstructor; -import lombok.Data; import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -49,7 +47,6 @@ @RestController @RequestMapping("/admin/api/event/{eventName}/waiting-queue") -@AllArgsConstructor public class AdminWaitingQueueApiController { private final WaitingQueueManager waitingQueueManager; @@ -59,6 +56,20 @@ public class AdminWaitingQueueApiController { private final EventStatisticsManager eventStatisticsManager; private final ClockProvider clockProvider; + public AdminWaitingQueueApiController(WaitingQueueManager waitingQueueManager, + EventManager eventManager, + TicketReservationManager ticketReservationManager, + ConfigurationManager configurationManager, + EventStatisticsManager eventStatisticsManager, + ClockProvider clockProvider) { + this.waitingQueueManager = waitingQueueManager; + this.eventManager = eventManager; + this.ticketReservationManager = ticketReservationManager; + this.configurationManager = configurationManager; + this.eventStatisticsManager = eventStatisticsManager; + this.clockProvider = clockProvider; + } + @GetMapping("/status") public Map<String, Boolean> getStatusForEvent(@PathVariable("eventName") String eventName, Principal principal) { return eventManager.getOptionalByName(eventName, principal.getName()) @@ -168,9 +179,16 @@ private ResponseEntity<Map<String, Object>> performStatusModification(String eve .orElseGet(() -> new ResponseEntity<>(HttpStatus.BAD_REQUEST)); } - @Data private static class SetStatusForm { private boolean status; + + public void setStatus(boolean status) { + this.status = status; + } + + public boolean isStatus() { + return status; + } } diff --git a/src/main/java/alfio/controller/api/admin/AttendeeBulkImportApiController.java b/src/main/java/alfio/controller/api/admin/AttendeeBulkImportApiController.java index 49da830bd2..ba258e2e4a 100644 --- a/src/main/java/alfio/controller/api/admin/AttendeeBulkImportApiController.java +++ b/src/main/java/alfio/controller/api/admin/AttendeeBulkImportApiController.java @@ -20,18 +20,20 @@ import alfio.model.AdminReservationRequestStats; import alfio.model.modification.AdminReservationModification; import alfio.model.result.Result; -import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.*; import java.security.Principal; @RequestMapping("/admin/api/event/{eventName}/attendees/import") @RestController -@AllArgsConstructor public class AttendeeBulkImportApiController { private final AdminReservationRequestManager requestManager; + public AttendeeBulkImportApiController(AdminReservationRequestManager requestManager) { + this.requestManager = requestManager; + } + @PostMapping("") public Result<String> createReservations(@PathVariable("eventName") String eventName, @RequestBody AdminReservationModification body, diff --git a/src/main/java/alfio/controller/api/admin/CheckInApiController.java b/src/main/java/alfio/controller/api/admin/CheckInApiController.java index 552bfef40d..a0a0f9bc4b 100644 --- a/src/main/java/alfio/controller/api/admin/CheckInApiController.java +++ b/src/main/java/alfio/controller/api/admin/CheckInApiController.java @@ -23,18 +23,18 @@ import alfio.manager.system.ConfigurationManager; import alfio.model.EventAndOrganizationId; import alfio.model.FullTicketInfo; +import alfio.model.checkin.AttendeeSearchResults; import alfio.model.system.ConfigurationKeys; import alfio.util.Json; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -46,26 +46,58 @@ import static alfio.util.Wrappers.optionally; -@Log4j2 @RestController @RequestMapping("/admin/api") -@RequiredArgsConstructor public class CheckInApiController { + private static final Logger log = LoggerFactory.getLogger(CheckInApiController.class); + private static final String ALFIO_TIMESTAMP_HEADER = "Alfio-TIME"; private final CheckInManager checkInManager; private final EventManager eventManager; private final ConfigurationManager configurationManager; - @Data + public CheckInApiController(CheckInManager checkInManager, + EventManager eventManager, + ConfigurationManager configurationManager) { + this.checkInManager = checkInManager; + this.eventManager = eventManager; + this.configurationManager = configurationManager; + } + + public static class TicketCode { private String code; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } } - @Data + public static class TicketIdentifierCode { private String identifier; private String code; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } } @GetMapping("/check-in/{eventId}/ticket/{ticketIdentifier}") @@ -120,18 +152,34 @@ public Map<String, TicketAndCheckInResult> bulkCheckIn(@PathVariable("eventName" public boolean manualCheckIn(@PathVariable("eventId") int eventId, @PathVariable("ticketIdentifier") String ticketIdentifier, Principal principal) { - log.warn("for event id : {} and ticket : {}, a manual check in has been done", eventId, ticketIdentifier); + log.warn("for event id : {} and ticket : {}, a manual check in has been done by {}", eventId, ticketIdentifier, principal.getName()); return checkInManager.manualCheckIn(eventId, ticketIdentifier, principal.getName()); } + @PostMapping("/check-in/event/{eventName}/ticket/{ticketIdentifier}/manual-check-in") + public ResponseEntity<Boolean> manualCheckIn(@PathVariable("eventName") String eventName, + @PathVariable("ticketIdentifier") String ticketIdentifier, + Principal principal) { + return ResponseEntity.of(eventManager.getOptionalEventAndOrganizationIdByName(eventName, principal.getName()) + .map(ev -> manualCheckIn(ev.getId(), ticketIdentifier, principal))); + } + @PostMapping("/check-in/{eventId}/ticket/{ticketIdentifier}/revert-check-in") public boolean revertCheckIn(@PathVariable("eventId") int eventId, @PathVariable("ticketIdentifier") String ticketIdentifier, Principal principal) { - log.warn("for event id : {} and ticket : {}, a revert of the check in has been done", eventId, ticketIdentifier); + log.warn("for event id : {} and ticket : {}, a revert of the check in has been done by {}", eventId, ticketIdentifier, principal.getName()); return checkInManager.revertCheckIn(eventId, ticketIdentifier, principal.getName()); } + @PostMapping("/check-in/event/{eventName}/ticket/{ticketIdentifier}/revert-check-in") + public ResponseEntity<Boolean> revertCheckIn(@PathVariable("eventName") String eventName, + @PathVariable("ticketIdentifier") String ticketIdentifier, + Principal principal) { + return ResponseEntity.of(eventManager.getOptionalEventAndOrganizationIdByName(eventName, principal.getName()) + .map(ev -> revertCheckIn(ev.getId(), ticketIdentifier, principal))); + } + @PostMapping("/check-in/event/{eventName}/ticket/{ticketIdentifier}/confirm-on-site-payment") public TicketAndCheckInResult confirmOnSitePayment(@PathVariable("eventName") String eventName, @PathVariable("ticketIdentifier") String ticketIdentifier, @@ -164,6 +212,20 @@ public List<Integer> findAllIdentifiersForAdminCheckIn(@PathVariable("eventId") return checkInManager.getAttendeesIdentifiers(eventId, changedSince == null ? new Date(0) : new Date(changedSince), principal.getName()); } + @GetMapping("/check-in/event/{publicIdentifier}/attendees") + public ResponseEntity<AttendeeSearchResults> searchAttendees(@PathVariable("publicIdentifier") String publicIdentifier, + @RequestParam(value = "query", required = false) String query, + @RequestParam(value = "page", required = false, defaultValue = "0") int page, + Principal principal) { + if (StringUtils.isBlank(query) || StringUtils.isBlank(publicIdentifier)) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + return ResponseEntity.of(eventManager.getOptionalByName(publicIdentifier, principal.getName()) + .map(event -> checkInManager.searchAttendees(event, query, page))); + + } + @PostMapping("/check-in/{eventId}/tickets") public List<FullTicketInfo> findAllTicketsForAdminCheckIn(@PathVariable("eventId") int eventId, @RequestBody List<Integer> ids, @@ -239,10 +301,8 @@ private Optional<LabelLayout> loadLabelLayout(EventAndOrganizationId event) { .flatMap(str -> optionally(() -> Json.fromJson(str, LabelLayout.class))); } - @Data - private static class OnSitePaymentConfirmation { - private final boolean status; - private final String message; + + private record OnSitePaymentConfirmation(@JsonProperty("status") boolean status, @JsonProperty("message") String message) { } @Getter diff --git a/src/main/java/alfio/controller/api/admin/ConfigurationApiController.java b/src/main/java/alfio/controller/api/admin/ConfigurationApiController.java index 2851856d0e..db2b37861f 100644 --- a/src/main/java/alfio/controller/api/admin/ConfigurationApiController.java +++ b/src/main/java/alfio/controller/api/admin/ConfigurationApiController.java @@ -29,11 +29,8 @@ import alfio.model.modification.ConfigurationModification; import alfio.model.system.Configuration; import alfio.model.system.ConfigurationKeys; -import alfio.model.user.Organization; import alfio.util.ClockProvider; import alfio.util.RequestUtils; -import lombok.AllArgsConstructor; -import lombok.Data; import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -52,7 +49,6 @@ @RestController @RequestMapping("/admin/api/configuration") -@AllArgsConstructor public class ConfigurationApiController { private final ConfigurationManager configurationManager; @@ -62,6 +58,20 @@ public class ConfigurationApiController { private final ClockProvider clockProvider; private final UserManager userManager; + public ConfigurationApiController(ConfigurationManager configurationManager, + BillingDocumentManager billingDocumentManager, + AdminJobManager adminJobManager, + EventManager eventManager, + ClockProvider clockProvider, + UserManager userManager) { + this.configurationManager = configurationManager; + this.billingDocumentManager = billingDocumentManager; + this.adminJobManager = adminJobManager; + this.eventManager = eventManager; + this.clockProvider = clockProvider; + this.userManager = userManager; + } + @GetMapping(value = "/load") public Map<ConfigurationKeys.SettingCategory, List<Configuration>> loadConfiguration(Principal principal) { return configurationManager.loadAllSystemConfigurationIncludingMissing(principal.getName()); @@ -266,15 +276,6 @@ public ResponseEntity<Boolean> generateTicketsForSubscriptions(@RequestParam(val return ResponseEntity.ok(adminJobManager.scheduleExecution(ASSIGN_TICKETS_TO_SUBSCRIBERS, requireNonNullElse(jobMetadata, Map.of()))); } - @Data - static class OrganizationConfig { - private final Organization organization; - private final Map<ConfigurationKeys.SettingCategory, List<Configuration>> config; - } - - @Data - static class InstanceSettings { - private final int descriptionMaxLength; - private final String baseUrl; + record InstanceSettings(int descriptionMaxLength, String baseUrl) { } } diff --git a/src/main/java/alfio/controller/api/admin/CustomMessagesApiController.java b/src/main/java/alfio/controller/api/admin/CustomMessagesApiController.java index cf8a18c949..be8919879e 100644 --- a/src/main/java/alfio/controller/api/admin/CustomMessagesApiController.java +++ b/src/main/java/alfio/controller/api/admin/CustomMessagesApiController.java @@ -18,7 +18,8 @@ import alfio.manager.support.CustomMessageManager; import alfio.model.modification.MessageModification; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @@ -30,9 +31,10 @@ @RestController @RequestMapping("/admin/api/events/{eventName}/messages") -@Log4j2 public class CustomMessagesApiController { + private static final Logger log = LoggerFactory.getLogger(CustomMessagesApiController.class); + private final CustomMessageManager customMessageManager; @Autowired diff --git a/src/main/java/alfio/controller/api/admin/EmailMessageApiController.java b/src/main/java/alfio/controller/api/admin/EmailMessageApiController.java index 5e01d18c89..0120b42012 100644 --- a/src/main/java/alfio/controller/api/admin/EmailMessageApiController.java +++ b/src/main/java/alfio/controller/api/admin/EmailMessageApiController.java @@ -22,8 +22,6 @@ import alfio.model.EmailMessage; import alfio.model.LightweightMailMessage; import alfio.model.PurchaseContext; -import lombok.AllArgsConstructor; -import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -36,7 +34,6 @@ import java.util.Optional; import java.util.stream.Collectors; -@RequiredArgsConstructor @RestController @RequestMapping("/admin/api/{purchaseContextType}/{publicIdentifier}/email") public class EmailMessageApiController { @@ -44,6 +41,12 @@ public class EmailMessageApiController { private final NotificationManager notificationManager; private final PurchaseContextManager purchaseContextManager; + public EmailMessageApiController(NotificationManager notificationManager, + PurchaseContextManager purchaseContextManager) { + this.notificationManager = notificationManager; + this.purchaseContextManager = purchaseContextManager; + } + @GetMapping("/") public PageAndContent<List<LightweightEmailMessage>> loadEmailMessages(@PathVariable("purchaseContextType") PurchaseContext.PurchaseContextType purchaseContextType, @PathVariable("publicIdentifier") String publicIdentifier, @@ -67,13 +70,19 @@ public LightweightEmailMessage loadEmailMessage(@PathVariable("purchaseContextTy return notificationManager.loadSingleMessageForPurchaseContext(purchaseContext, messageId).map(m -> new LightweightEmailMessage(m, purchaseContext.getZoneId(), false)).orElseThrow(IllegalArgumentException::new); } - @AllArgsConstructor + private static final class LightweightEmailMessage { @Delegate(excludes = LightweightExclusions.class) private final EmailMessage src; private final ZoneId eventZoneId; private final boolean list; + private LightweightEmailMessage(EmailMessage src, ZoneId eventZoneId, boolean list) { + this.src = src; + this.eventZoneId = eventZoneId; + this.list = list; + } + public String getAttachments() { return null; } diff --git a/src/main/java/alfio/controller/api/admin/EventApiController.java b/src/main/java/alfio/controller/api/admin/EventApiController.java index fb724dc40e..0b708e17e7 100644 --- a/src/main/java/alfio/controller/api/admin/EventApiController.java +++ b/src/main/java/alfio/controller/api/admin/EventApiController.java @@ -45,16 +45,13 @@ import alfio.util.*; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvException; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.SneakyThrows; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -87,15 +84,14 @@ import static alfio.util.Validator.*; import static alfio.util.Wrappers.optionally; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.stream.Collectors.toList; import static org.apache.commons.lang3.StringUtils.defaultString; @RestController @RequestMapping("/admin/api") -@Log4j2 -@AllArgsConstructor public class EventApiController { + private static final Logger log = LoggerFactory.getLogger(EventApiController.class); + private static final String OK = "OK"; private static final String CUSTOM_FIELDS_PREFIX = "custom:"; private final EventManager eventManager; @@ -115,6 +111,40 @@ public class EventApiController { private final ExtensionManager extensionManager; private final ClockProvider clockProvider; + public EventApiController(EventManager eventManager, + EventStatisticsManager eventStatisticsManager, + I18nManager i18nManager, + TicketReservationManager ticketReservationManager, + TicketFieldRepository ticketFieldRepository, + EventDescriptionRepository eventDescriptionRepository, + TicketHelper ticketHelper, + DynamicFieldTemplateRepository dynamicFieldTemplateRepository, + UserManager userManager, + SponsorScanRepository sponsorScanRepository, + PaymentManager paymentManager, + TemplateManager templateManager, + FileUploadManager fileUploadManager, + ConfigurationManager configurationManager, + ExtensionManager extensionManager, + ClockProvider clockProvider) { + this.eventManager = eventManager; + this.eventStatisticsManager = eventStatisticsManager; + this.i18nManager = i18nManager; + this.ticketReservationManager = ticketReservationManager; + this.ticketFieldRepository = ticketFieldRepository; + this.eventDescriptionRepository = eventDescriptionRepository; + this.ticketHelper = ticketHelper; + this.dynamicFieldTemplateRepository = dynamicFieldTemplateRepository; + this.userManager = userManager; + this.sponsorScanRepository = sponsorScanRepository; + this.paymentManager = paymentManager; + this.templateManager = templateManager; + this.fileUploadManager = fileUploadManager; + this.configurationManager = configurationManager; + this.extensionManager = extensionManager; + this.clockProvider = clockProvider; + } + @ExceptionHandler(DataAccessException.class) public String exception(DataAccessException e) { @@ -142,13 +172,15 @@ public List<PaymentManager.PaymentMethodDTO> getPaymentProxies( @PathVariable("o } @GetMapping(value = "/events", headers = "Authorization") - public List<EventListItem> getAllEventsForExternal(Principal principal, HttpServletRequest request) { - List<Integer> userOrganizations = userManager.findUserOrganizations(principal.getName()).stream().map(Organization::getId).collect(toList()); + public List<EventListItem> getAllEventsForExternal(Principal principal, + HttpServletRequest request, + @RequestParam(value = "includeOnline", required = false, defaultValue = "false") boolean includeOnline) { + List<Integer> userOrganizations = userManager.findUserOrganizations(principal.getName()).stream().map(Organization::getId).toList(); return eventManager.getActiveEvents().stream() - .filter(e -> userOrganizations.contains(e.getOrganizationId())) + .filter(e -> userOrganizations.contains(e.getOrganizationId()) && (includeOnline || e.getFormat() != Event.EventFormat.ONLINE)) .sorted(Comparator.comparing(e -> e.getBegin().withZoneSameInstant(ZoneId.systemDefault()))) .map(s -> new EventListItem(s, request.getContextPath(), eventDescriptionRepository.findByEventId(s.getId()))) - .collect(toList()); + .toList(); } @GetMapping("/events") @@ -156,6 +188,12 @@ public List<EventStatistic> getAllEvents(Principal principal) { return eventStatisticsManager.getAllEventsWithStatistics(principal.getName()); } + @GetMapping("/events-count") + public ResponseEntity<Integer> getEventsCount() { + return ResponseEntity.ok(eventManager.getEventsCount()); + } + + @GetMapping("/active-events") public List<EventStatistic> getAllActiveEvents(Principal principal) { return eventStatisticsManager.getAllEventsWithStatisticsFilteredBy(principal.getName(), event -> !event.expiredSince(14)); @@ -170,11 +208,7 @@ public List<EventStatistic> getAllExpiredEvents(Principal principal) { } - @AllArgsConstructor - @Getter - public static class EventAndOrganization { - private final EventWithAdditionalInfo event; - private final Organization organization; + public record EventAndOrganization(EventWithAdditionalInfo event, Organization organization) { } @@ -310,8 +344,8 @@ public ResponseEntity<String> rearrangeCategories(@PathVariable("eventName") Str } private static final String PAYMENT_METHOD = "Payment Method"; - private static final List<String> FIXED_FIELDS = Arrays.asList("ID", "Category", "Event", "Status", "OriginalPrice", "PaidPrice", "Discount", "VAT", "ReservationID", "Full Name", "First Name", "Last Name", "E-Mail", "Locked", "Language", "Confirmation", "Billing Address", "Country Code", "Payment ID", PAYMENT_METHOD); - private static final List<SerializablePair<String, String>> FIXED_PAIRS = FIXED_FIELDS.stream().map(f -> SerializablePair.of(f, f)).collect(toList()); + private static final List<String> FIXED_FIELDS = Arrays.asList("ID", "Category", "Event", "Status", "OriginalPrice", "PaidPrice", "Discount", "VAT", "ReservationID", "Full Name", "First Name", "Last Name", "E-Mail", "Locked", "Language", "Confirmation", "Billing Address", "Country Code", "Voucher Code", "Payment ID", PAYMENT_METHOD); + private static final List<SerializablePair<String, String>> FIXED_PAIRS = FIXED_FIELDS.stream().map(f -> SerializablePair.of(f, f)).toList(); private static final String FISCAL_CODE = "Fiscal Code"; private static final String REFERENCE_TYPE = "Reference Type"; private static final List<String> ITALIAN_E_INVOICING_FIELDS = List.of(FISCAL_CODE, REFERENCE_TYPE, "Addressee Code", "PEC"); @@ -381,6 +415,7 @@ private Stream<String[]> exportLines(String eventName, Principal principal, List if(fields.contains("Confirmation")) {line.add(reservation.getConfirmationTimestamp().withZoneSameInstant(eventZoneId).toString());} if(fields.contains("Billing Address")) {line.add(reservation.getBillingAddress());} if(fields.contains("Country Code")) {line.add(reservation.getVatCountryCode());} + if(fields.contains("Voucher Code")) {line.add(trs.getPromoCode());} boolean paymentIdRequested = fields.contains("Payment ID"); boolean paymentGatewayRequested = fields.contains(PAYMENT_METHOD); if((paymentIdRequested || paymentGatewayRequested)) { @@ -423,9 +458,10 @@ public void downloadSponsorScanExport(@PathVariable("eventName") String eventNam header.add("Timestamp"); header.add("Full name"); header.add("Email"); - header.addAll(fields.stream().map(TicketFieldConfiguration::getName).collect(toList())); + header.addAll(fields.stream().map(TicketFieldConfiguration::getName).toList()); header.add("Sponsor notes"); header.add("Lead Status"); + header.add("Operator"); Stream<String[]> sponsorScans = userManager.findAllEnabledUsers(principal.getName()).stream() .map(u -> Pair.of(u, userManager.getUserRole(u))) @@ -436,12 +472,12 @@ public void downloadSponsorScanExport(@PathVariable("eventName") String eventNam .map(p -> { DetailedScanData data = p.getLeft(); Map<String, String> descriptions = p.getRight(); - return Pair.of(data, fields.stream().map(x -> descriptions.getOrDefault(x.getName(), "")).collect(toList())); + return Pair.of(data, fields.stream().map(x -> descriptions.getOrDefault(x.getName(), "")).toList()); }).map(p -> { List<String> line = new ArrayList<>(); Ticket ticket = p.getLeft().getTicket(); SponsorScan sponsorScan = p.getLeft().getSponsorScan(); - User user = userManager.findUser(sponsorScan.getUserId()); + User user = userManager.findUser(sponsorScan.getUserId(), principal); line.add(user.getUsername()); line.add(user.getDescription()); line.add(sponsorScan.getTimestamp().toString()); @@ -452,6 +488,7 @@ public void downloadSponsorScanExport(@PathVariable("eventName") String eventNam line.add(sponsorScan.getNotes()); line.add(sponsorScan.getLeadStatus().name()); + line.add(sponsorScan.getOperator()); return line.toArray(new String[0]); }); @@ -480,9 +517,9 @@ public List<SerializablePair<String, String>> getAllFields(@PathVariable("eventN var eventAndOrganizationId = eventManager.getEventAndOrganizationId(eventName, principal.getName()); List<SerializablePair<String, String>> fields = new ArrayList<>(FIXED_PAIRS); if(configurationManager.isItalianEInvoicingEnabled(eventAndOrganizationId)) { - fields.addAll(ITALIAN_E_INVOICING_FIELDS.stream().map(f -> SerializablePair.of(f, f)).collect(toList())); + fields.addAll(ITALIAN_E_INVOICING_FIELDS.stream().map(f -> SerializablePair.of(f, f)).toList()); } - fields.addAll(ticketFieldRepository.findFieldsForEvent(eventName).stream().map(f -> SerializablePair.of(CUSTOM_FIELDS_PREFIX + f, f)).collect(toList())); + fields.addAll(ticketFieldRepository.findFieldsForEvent(eventName).stream().map(f -> SerializablePair.of(CUSTOM_FIELDS_PREFIX + f, f)).toList()); return fields; } @@ -491,7 +528,7 @@ public List<TicketFieldConfigurationAndAllDescriptions> getAllAdditionalField(@P final Map<Integer, List<TicketFieldDescription>> descById = ticketFieldRepository.findDescriptions(eventName).stream().collect(Collectors.groupingBy(TicketFieldDescription::getTicketFieldConfigurationId)); return ticketFieldRepository.findAdditionalFieldsForEvent(eventName).stream() .map(field -> new TicketFieldConfigurationAndAllDescriptions(field, descById.getOrDefault(field.getId(), Collections.emptyList()))) - .collect(toList()); + .toList(); } @GetMapping("/events/{eventName}/additional-field/{id}/stats") @@ -561,9 +598,12 @@ public Integer getPendingPaymentsCount(@PathVariable("eventName") String eventNa } @PostMapping("/events/{eventName}/pending-payments/{reservationId}/confirm") - public String confirmPayment(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, Principal principal) { + public String confirmPayment(@PathVariable("eventName") String eventName, + @PathVariable("reservationId") String reservationId, + @RequestBody TransactionMetadataModification transactionMetadataModification, + Principal principal) { var event = loadEvent(eventName, principal); - ticketReservationManager.confirmOfflinePayment(event, reservationId, principal.getName()); + ticketReservationManager.confirmOfflinePayment(event, reservationId, transactionMetadataModification, principal.getName()); ticketReservationManager.findById(reservationId) .filter(TicketReservation::isDirectAssignmentRequested) .ifPresent(reservation -> { @@ -577,8 +617,9 @@ public String confirmPayment(@PathVariable("eventName") String eventName, @PathV public String deletePendingPayment(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, @RequestParam(required = false, value = "credit", defaultValue = "false") Boolean creditReservation, + @RequestParam(required = false, value = "notify", defaultValue = "true") Boolean notify, Principal principal) { - ticketReservationManager.deleteOfflinePayment(loadEvent(eventName, principal), reservationId, false, Boolean.TRUE.equals(creditReservation), principal.getName()); + ticketReservationManager.deleteOfflinePayment(loadEvent(eventName, principal), reservationId, false, Boolean.TRUE.equals(creditReservation), notify, principal.getName()); return OK; } @@ -601,7 +642,7 @@ public List<Triple<Boolean, String, String>> bulkConfirmation(@PathVariable("eve return Triple.of(Boolean.FALSE, Optional.ofNullable(reservationID).orElse(""), e.getMessage()); } }) - .collect(toList()); + .toList(); } } @@ -619,9 +660,9 @@ public List<ContentLanguage> getAvailableLocales(@PathVariable("eventName") Stri } @GetMapping("/events/{eventName}/invoices/count") - public Integer countInvoicesForEvent(@PathVariable("eventName") String eventName, Principal principal) { + public Integer countBillingDocumentsForEvent(@PathVariable("eventName") String eventName, Principal principal) { return eventManager.getOptionalEventAndOrganizationIdByName(eventName, principal.getName()) - .map(e -> ticketReservationManager.countInvoices(e.getId())) + .map(e -> ticketReservationManager.countBillingDocuments(e.getId())) .orElse(0); } @@ -643,29 +684,30 @@ public void getAllInvoices(@PathVariable("eventName") String eventName, HttpServ } } - @SneakyThrows private void addPdfToZip(Event event, ZipOutputStream zipOS, TicketReservation reservation, BillingDocument document) { Map<String, Object> reservationModel = document.getModel(); Optional<byte[]> pdf; var language = LocaleUtil.forLanguageTag(reservation.getUserLanguage()); - switch(document.getType()) { - case CREDIT_NOTE: - pdf = TemplateProcessor.buildCreditNotePdf(event, fileUploadManager, language, templateManager, reservationModel, extensionManager); - break; - case RECEIPT: - pdf = TemplateProcessor.buildReceiptPdf(event, fileUploadManager, language, templateManager, reservationModel, extensionManager); - break; - default: - pdf = TemplateProcessor.buildInvoicePdf(event, fileUploadManager, language, templateManager, reservationModel, extensionManager); - } + pdf = switch (document.getType()) { + case CREDIT_NOTE -> + TemplateProcessor.buildCreditNotePdf(event, fileUploadManager, language, templateManager, reservationModel, extensionManager); + case RECEIPT -> + TemplateProcessor.buildReceiptPdf(event, fileUploadManager, language, templateManager, reservationModel, extensionManager); + default -> + TemplateProcessor.buildInvoicePdf(event, fileUploadManager, language, templateManager, reservationModel, extensionManager); + }; if (pdf.isPresent()) { String fileName = FileUtil.getBillingDocumentFileName(event.getShortName(), reservation.getId(), document); var entry = new ZipEntry(fileName); entry.setTimeLocal(document.getGenerationTimestamp().withZoneSameInstant(event.getZoneId()).toLocalDateTime()); - zipOS.putNextEntry(entry); - StreamUtils.copy(pdf.get(), zipOS); + try { + zipOS.putNextEntry(entry); + StreamUtils.copy(pdf.get(), zipOS); + } catch (IOException e) { + throw new IllegalStateException(e); + } } } @@ -861,11 +903,9 @@ private Event loadEvent(String eventName, Principal principal) { return singleEvent.orElseThrow(); } - @Data - static class TicketsStatistics { - private final String granularity; - private final List<TicketsByDateStatistic> sold; - private final List<TicketsByDateStatistic> reserved; + record TicketsStatistics(String granularity, + List<TicketsByDateStatistic> sold, + List<TicketsByDateStatistic> reserved) { } } diff --git a/src/main/java/alfio/controller/api/admin/ExportApiController.java b/src/main/java/alfio/controller/api/admin/ExportApiController.java new file mode 100644 index 0000000000..70ed2c1c7d --- /dev/null +++ b/src/main/java/alfio/controller/api/admin/ExportApiController.java @@ -0,0 +1,141 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.admin; + +import alfio.manager.ExportManager; +import alfio.model.ReservationsByEvent; +import alfio.model.support.ReservationInfo; +import alfio.model.support.TicketInfo; +import alfio.util.MonetaryUtil; +import ch.digitalfondue.basicxlsx.StreamingWorkbook; +import ch.digitalfondue.basicxlsx.Style; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +import static alfio.util.ExportUtils.addSheetToWorkbook; +import static alfio.util.ExportUtils.exportExcel; +import static java.util.Objects.*; + +@RestController +@RequestMapping("/admin/api/export") +public class ExportApiController { + + private final ExportManager exportManager; + + public ExportApiController(ExportManager exportManager) { + this.exportManager = exportManager; + } + + @GetMapping("/reservations") + public void downloadAllEvents(@RequestParam(name = "from") String from, + @RequestParam(name = "to") String to, + HttpServletResponse response, + Principal principal) throws IOException { + var allEvents = exportManager.reservationsForInterval(LocalDate.parse(requireNonNull(from)), + LocalDate.parse(requireNonNull(to)), requireNonNull(principal)); + if (allEvents.isEmpty()) { + response.setContentType("text/plain"); + response.setStatus(HttpStatus.PRECONDITION_REQUIRED.value()); + response.getWriter().write("No reservations found for the selected period"); + } else { + exportExcel("all-reservations.xlsx", response, workbook -> writeSheets(allEvents, workbook)); + } + } + + private static void writeSheets(List<ReservationsByEvent> allEvents, StreamingWorkbook workbook) { + var header = new String[] { + "Event Name", + "Reservation ID", + "Confirmation Date", + "Billed to", + "Tax ID", + "Tax Code", + "Invoice #", + "Amount", + "Tax", + "Currency", + "Payment Type", + "Ticket ID", + "Ticket Type", + "Ticket Amount", + "Ticket Tax", + "Attendee", + "Status" + }; + var headerStyle = workbook.defineStyle().font().bold(true).build(); + allEvents.stream().sorted(Comparator.comparing(ReservationsByEvent::getEventShortName)) + .forEach(e -> addSheet(workbook, header, headerStyle, e)); + } + + private static void addSheet(StreamingWorkbook workbook, String[] header, Style headerStyle, ReservationsByEvent eventWithReservations) { + var rowData = eventWithReservations.getReservations().stream() + .sorted(Comparator.comparing(ReservationInfo::getConfirmationTimestamp)) + .flatMap(r -> ticketRows(eventWithReservations, r)); + addSheetToWorkbook(eventWithReservations.getEventShortName(), header, rowData, workbook, headerStyle); + } + + private static Stream<String[]> ticketRows(ReservationsByEvent eventWithReservations, ReservationInfo r) { + return r.getTickets().stream() + .map(t -> buildTicketRow(eventWithReservations, r, t)); + } + + private static String[] buildTicketRow(ReservationsByEvent eventWithReservations, + ReservationInfo r, + TicketInfo t) { + return new String[]{ + eventWithReservations.getDisplayName(), + r.getId(), + r.getConfirmationTimestamp(), + billingCompanyOrFullName(r), + r.getTaxId(), + r.getTaxCode(), + r.getInvoiceNumber(), + formatAmount(r.getSrcPriceCts(), r.getCurrency()), + formatAmount(r.getTaxCts(), r.getCurrency()), + r.getCurrency(), + r.getPaymentType().name(), + t.getId(), + t.getType(), + formatAmount(t.getSrcPriceCts(), r.getCurrency()), + formatAmount(t.getTaxCts(), r.getCurrency()), + (requireNonNullElse(t.getFirstName(), "") + " " + requireNonNullElse(t.getLastName(), "")).trim(), + t.getStatus() + }; + } + + private static String billingCompanyOrFullName(ReservationInfo r) { + return requireNonNullElseGet(r.getCompanyName(), () -> r.getFirstName() + " " + r.getLastName()); + } + + private static String formatAmount(Integer originalCts, String currency) { + if (originalCts == null) { + return ""; + } + return MonetaryUtil.formatCents(originalCts, currency); + } +} diff --git a/src/main/java/alfio/controller/api/admin/ExtensionApiController.java b/src/main/java/alfio/controller/api/admin/ExtensionApiController.java index be8f83a57e..4c14c66a34 100644 --- a/src/main/java/alfio/controller/api/admin/ExtensionApiController.java +++ b/src/main/java/alfio/controller/api/admin/ExtensionApiController.java @@ -30,11 +30,11 @@ import alfio.model.user.User; import alfio.repository.EventRepository; import alfio.repository.user.OrganizationRepository; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; import org.springframework.util.StreamUtils; @@ -51,11 +51,11 @@ import java.util.stream.Collectors; @RestController -@AllArgsConstructor @RequestMapping("/admin/api/extensions") -@Log4j2 public class ExtensionApiController { + private static final Logger log = LoggerFactory.getLogger(ExtensionApiController.class); + private static final String SAMPLE_JS; @@ -72,6 +72,16 @@ public class ExtensionApiController { private final OrganizationRepository organizationRepository; private final EventRepository eventRepository; + public ExtensionApiController(ExtensionService extensionService, + UserManager userManager, + OrganizationRepository organizationRepository, + EventRepository eventRepository) { + this.extensionService = extensionService; + this.userManager = userManager; + this.organizationRepository = organizationRepository; + this.eventRepository = eventRepository; + } + @GetMapping("") public List<ExtensionSupport> listAll(Principal principal) { diff --git a/src/main/java/alfio/controller/api/admin/FileUploadApiController.java b/src/main/java/alfio/controller/api/admin/FileUploadApiController.java index 30c5a63c7e..4120c8e067 100644 --- a/src/main/java/alfio/controller/api/admin/FileUploadApiController.java +++ b/src/main/java/alfio/controller/api/admin/FileUploadApiController.java @@ -18,29 +18,20 @@ import alfio.manager.FileUploadManager; import alfio.model.modification.UploadBase64FileModification; -import lombok.extern.log4j.Log4j2; - -import org.imgscalr.Scalr; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -import org.springframework.util.MimeType; -import org.springframework.util.MimeTypeUtils; -import org.springframework.web.bind.annotation.*; - -import javax.imageio.ImageIO; - -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +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; @RestController @RequestMapping("/admin/api") -@Log4j2 public class FileUploadApiController { - private static final int IMAGE_THUMB_MAX_WIDTH_PX = 500; - private static final int IMAGE_THUMB_MAX_HEIGHT_PX = 500; + private static final Logger log = LoggerFactory.getLogger(FileUploadApiController.class); private final FileUploadManager fileUploadManager; @@ -50,36 +41,12 @@ public FileUploadApiController(FileUploadManager fileUploadManager) { } @PostMapping("/file/upload") - public ResponseEntity<String> uploadFile(@RequestParam(required = false, value = "resizeImage", defaultValue = "false") Boolean resizeImage, - @RequestBody UploadBase64FileModification upload) { - + public ResponseEntity<String> uploadFile(@RequestBody UploadBase64FileModification upload) { try { - final var mimeType = MimeTypeUtils.parseMimeType(upload.getType()); - if (Boolean.TRUE.equals(resizeImage)) { - upload = resize(upload, mimeType); - } return ResponseEntity.ok(fileUploadManager.insertFile(upload)); } catch (Exception e) { log.error("error while uploading image", e); return ResponseEntity.badRequest().build(); } } - - private UploadBase64FileModification resize(UploadBase64FileModification upload, MimeType mimeType) throws IOException { - BufferedImage image = ImageIO.read(new ByteArrayInputStream(upload.getFile())); - //resize only if the image is bigger than 500px on one of the side - if (image.getWidth() > IMAGE_THUMB_MAX_WIDTH_PX || image.getHeight() > IMAGE_THUMB_MAX_HEIGHT_PX) { - UploadBase64FileModification resized = new UploadBase64FileModification(); - BufferedImage thumbImg = Scalr.resize(image, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, IMAGE_THUMB_MAX_WIDTH_PX, IMAGE_THUMB_MAX_HEIGHT_PX, Scalr.OP_ANTIALIAS); - try (final var baos = new ByteArrayOutputStream()) { - ImageIO.write(thumbImg, mimeType.getSubtype(), baos); - resized.setFile(baos.toByteArray()); - } - resized.setAttributes(upload.getAttributes()); - resized.setName(upload.getName()); - resized.setType(upload.getType()); - return resized; - } - return upload; - } } diff --git a/src/main/java/alfio/controller/api/admin/GroupApiController.java b/src/main/java/alfio/controller/api/admin/GroupApiController.java index 8c832a4f6c..38cf74525f 100644 --- a/src/main/java/alfio/controller/api/admin/GroupApiController.java +++ b/src/main/java/alfio/controller/api/admin/GroupApiController.java @@ -26,7 +26,6 @@ import alfio.model.modification.LinkedGroupModification; import alfio.model.result.ErrorCode; import alfio.model.result.Result; -import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -39,13 +38,20 @@ @RestController @RequestMapping("/admin/api/group") -@RequiredArgsConstructor public class GroupApiController { private final GroupManager groupManager; private final UserManager userManager; private final EventManager eventManager; + public GroupApiController(GroupManager groupManager, + UserManager userManager, + EventManager eventManager) { + this.groupManager = groupManager; + this.userManager = userManager; + this.eventManager = eventManager; + } + @ExceptionHandler(DuplicateGroupItemException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public String handleDuplicateGroupItemException(DuplicateGroupItemException exc) { diff --git a/src/main/java/alfio/controller/api/admin/LocationApiController.java b/src/main/java/alfio/controller/api/admin/LocationApiController.java index 3fb233d56c..97dc44ae91 100644 --- a/src/main/java/alfio/controller/api/admin/LocationApiController.java +++ b/src/main/java/alfio/controller/api/admin/LocationApiController.java @@ -21,9 +21,9 @@ import alfio.model.modification.support.LocationDescriptor; import alfio.model.system.ConfigurationKeys; import com.moodysalem.TimezoneMapper; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @@ -35,9 +35,10 @@ @RestController @RequestMapping("/admin/api") -@Log4j2 public class LocationApiController { + private static final Logger log = LoggerFactory.getLogger(LocationApiController.class); + private final ConfigurationManager configurationManager; @Autowired @@ -88,10 +89,15 @@ public ProviderAndKeys getGeoInfoProviderAndKeys() { return new ProviderAndKeys(provider, apiKeys); } - @AllArgsConstructor + @Getter public static class ProviderAndKeys { private final ConfigurationKeys.GeoInfoProvider provider; - private Map<ConfigurationKeys, String> keys; + private final Map<ConfigurationKeys, String> keys; + + public ProviderAndKeys(GeoInfoProvider provider, Map<ConfigurationKeys, String> keys) { + this.provider = provider; + this.keys = keys; + } } } diff --git a/src/main/java/alfio/controller/api/admin/PollAdminApiController.java b/src/main/java/alfio/controller/api/admin/PollAdminApiController.java index 44ca9c318e..228d9cf0d1 100644 --- a/src/main/java/alfio/controller/api/admin/PollAdminApiController.java +++ b/src/main/java/alfio/controller/api/admin/PollAdminApiController.java @@ -23,7 +23,6 @@ import alfio.model.poll.PollStatistics; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.RequiredArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.ResponseEntity; @@ -34,11 +33,14 @@ @RestController @RequestMapping("/admin/api/{eventName}/poll") -@RequiredArgsConstructor public class PollAdminApiController { private final PollManager pollManager; + public PollAdminApiController(PollManager pollManager) { + this.pollManager = pollManager; + } + @GetMapping ResponseEntity<List<PollModification>> getAllForEvent(@PathVariable("eventName") String eventName) { if(StringUtils.isEmpty(eventName)) { diff --git a/src/main/java/alfio/controller/api/admin/PromoCodeDiscountApiController.java b/src/main/java/alfio/controller/api/admin/PromoCodeDiscountApiController.java index 840c1f108f..23139d5ce0 100644 --- a/src/main/java/alfio/controller/api/admin/PromoCodeDiscountApiController.java +++ b/src/main/java/alfio/controller/api/admin/PromoCodeDiscountApiController.java @@ -17,49 +17,60 @@ package alfio.controller.api.admin; import alfio.manager.EventManager; +import alfio.manager.PromoCodeRequestManager; import alfio.model.PromoCodeDiscount; +import alfio.model.PromoCodeUsageResult; import alfio.model.modification.PromoCodeDiscountModification; import alfio.model.modification.PromoCodeDiscountWithFormattedTimeAndAmount; import alfio.repository.EventRepository; import alfio.repository.PromoCodeDiscountRepository; import alfio.util.ClockProvider; -import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import org.springframework.web.bind.annotation.*; +import java.security.Principal; import java.time.ZoneId; import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.util.List; -import java.util.Optional; - -import static alfio.model.PromoCodeDiscount.categoriesOrNull; @RestController @RequestMapping("/admin/api") -@RequiredArgsConstructor public class PromoCodeDiscountApiController { private final EventRepository eventRepository; - private final PromoCodeDiscountRepository promoCodeRepository; private final EventManager eventManager; - private final ClockProvider clockProvider; + private final PromoCodeRequestManager promoCodeRequestManager; + + public PromoCodeDiscountApiController(EventRepository eventRepository, + EventManager eventManager, + PromoCodeRequestManager promoCodeRequestManager) { + this.eventRepository = eventRepository; + this.eventManager = eventManager; + this.promoCodeRequestManager = promoCodeRequestManager; + } @PostMapping("/promo-code") public void addPromoCode(@RequestBody PromoCodeDiscountModification promoCode) { Integer eventId = promoCode.getEventId(); Integer organizationId = promoCode.getOrganizationId(); ZoneId zoneId = zoneIdFromEventId(eventId, promoCode.getUtcOffset()); - - int discount = promoCode.getDiscountValue(eventRepository.getEventCurrencyCode(eventId)); + + if(eventId != null && PromoCodeDiscount.supportsCurrencyCode(promoCode.getCodeType(), promoCode.getDiscountType())) { + String eventCurrencyCode = eventRepository.getEventCurrencyCode(eventId); + Validate.isTrue(eventCurrencyCode.equals(promoCode.getCurrencyCode()), "Currency code does not match"); + } + + int discount = promoCode.getDiscountValue(); eventManager.addPromoCode(promoCode.getPromoCode(), eventId, organizationId, promoCode.getStart().toZonedDateTime(zoneId), promoCode.getEnd().toZonedDateTime(zoneId), discount, promoCode.getDiscountType(), promoCode.getCategories(), promoCode.getMaxUsage(), - promoCode.getDescription(), promoCode.getEmailReference(), promoCode.getCodeType(), promoCode.getHiddenCategoryId()); + promoCode.getDescription(), promoCode.getEmailReference(), promoCode.getCodeType(), promoCode.getHiddenCategoryId(), promoCode.getCurrencyCode()); } @PostMapping("/promo-code/{promoCodeId}") public void updatePromoCode(@PathVariable("promoCodeId") int promoCodeId, @RequestBody PromoCodeDiscountModification promoCode) { - PromoCodeDiscount pcd = promoCodeRepository.findById(promoCodeId); + PromoCodeDiscount pcd = promoCodeRequestManager.findById(promoCodeId).orElseThrow(); ZoneId zoneId = zoneIdFromEventId(pcd.getEventId(), promoCode.getUtcOffset()); eventManager.updatePromoCode(promoCodeId, promoCode.getStart().toZonedDateTime(zoneId), promoCode.getEnd().toZonedDateTime(zoneId), promoCode.getMaxUsage(), promoCode.getCategories(), @@ -91,15 +102,22 @@ public void removePromoCode(@PathVariable("promoCodeId") int promoCodeId) { @PostMapping("/promo-code/{promoCodeId}/disable") public void disablePromoCode(@PathVariable("promoCodeId") int promoCodeId) { - promoCodeRepository.updateEventPromoCodeEnd(promoCodeId, ZonedDateTime.now(clockProvider.getClock())); + promoCodeRequestManager.disablePromoCode(promoCodeId); } @GetMapping("/promo-code/{promoCodeId}/count-use") public int countPromoCodeUse(@PathVariable("promoCodeId") int promoCodeId) { - Optional<PromoCodeDiscount> code = promoCodeRepository.findOptionalById(promoCodeId); - if(code.isEmpty()) { - return 0; + return promoCodeRequestManager.countUsage(promoCodeId); + } + + @GetMapping("/promo-code/{promoCodeId}/detailed-usage") + public List<PromoCodeUsageResult> retrieveDetailedUsage(@PathVariable("promoCodeId") int promoCodeId, + @RequestParam(value = "eventShortName", required = false) String eventShortName, + Principal principal) { + Integer eventId = null; + if (StringUtils.isNotBlank(eventShortName)) { + eventId = eventManager.getEventAndOrganizationId(eventShortName, principal.getName()).getId(); } - return promoCodeRepository.countConfirmedPromoCode(promoCodeId, categoriesOrNull(code.get()), null, categoriesOrNull(code.get()) != null ? "X" : null); + return promoCodeRequestManager.retrieveDetailedUsage(promoCodeId, eventId); } } diff --git a/src/main/java/alfio/controller/api/admin/ResourceController.java b/src/main/java/alfio/controller/api/admin/ResourceController.java index 5446b263cc..b799234219 100644 --- a/src/main/java/alfio/controller/api/admin/ResourceController.java +++ b/src/main/java/alfio/controller/api/admin/ResourceController.java @@ -19,14 +19,14 @@ import alfio.controller.support.TemplateProcessor; import alfio.manager.ExtensionManager; import alfio.manager.FileUploadManager; +import alfio.manager.SubscriptionManager; import alfio.manager.UploadedResourceManager; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.user.UserManager; -import alfio.model.ContentLanguage; -import alfio.model.Event; -import alfio.model.PriceContainer; -import alfio.model.UploadedResource; +import alfio.model.*; import alfio.model.modification.UploadBase64FileModification; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.transaction.PaymentProxy; import alfio.model.user.Organization; import alfio.repository.EventRepository; import alfio.repository.user.OrganizationRepository; @@ -35,9 +35,9 @@ import alfio.util.TemplateManager; import alfio.util.TemplateResource; import com.samskivert.mustache.MustacheException; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -55,20 +55,18 @@ import java.security.Principal; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @RestController @RequestMapping("/admin/api") -@Log4j2 -@AllArgsConstructor public class ResourceController { + private static final Logger log = LoggerFactory.getLogger(ResourceController.class); + private static final String TIMEZONE = "Europe/Zurich"; private final UploadedResourceManager uploadedResourceManager; private final UserManager userManager; private final EventRepository eventRepository; @@ -78,6 +76,29 @@ public class ResourceController { private final FileUploadManager fileUploadManager; private final ExtensionManager extensionManager; private final ClockProvider clockProvider; + private final SubscriptionManager subscriptionManager; + + public ResourceController(UploadedResourceManager uploadedResourceManager, + UserManager userManager, + EventRepository eventRepository, + MessageSourceManager messageSourceManager, + TemplateManager templateManager, + OrganizationRepository organizationRepository, + FileUploadManager fileUploadManager, + ExtensionManager extensionManager, + ClockProvider clockProvider, + SubscriptionManager subscriptionManager) { + this.uploadedResourceManager = uploadedResourceManager; + this.userManager = userManager; + this.eventRepository = eventRepository; + this.messageSourceManager = messageSourceManager; + this.templateManager = templateManager; + this.organizationRepository = organizationRepository; + this.fileUploadManager = fileUploadManager; + this.extensionManager = extensionManager; + this.clockProvider = clockProvider; + this.subscriptionManager = subscriptionManager; + } @ExceptionHandler(Exception.class) @@ -112,6 +133,7 @@ public void getTemplate(@PathVariable("name") TemplateResource name, @PathVariab public void previewTemplate(@PathVariable("name") TemplateResource name, @PathVariable("locale") String locale, @RequestParam(required = false, value = "organizationId") Integer organizationId, @RequestParam(required = false, value = "eventId") Integer eventId, + @RequestParam(required = false, value = "subscriptionDescriptorId") UUID subscriptionDescriptorId, @RequestBody UploadBase64FileModification template, Principal principal, HttpServletResponse response) throws IOException { @@ -120,23 +142,12 @@ public void previewTemplate(@PathVariable("name") TemplateResource name, @PathVa Locale loc = LocaleUtil.forLanguageTag(locale); if (organizationId != null) { - Event event; - if (eventId!= null) { - checkAccess(organizationId, eventId, principal); - event = eventRepository.findById(eventId); - } else { - checkAccess(organizationId, principal); - var zoneId = ZoneId.of("Europe/Zurich"); - event = new Event(-1, Event.EventFormat.IN_PERSON, "TEST", "TEST", "TEST", "0", "0", ZonedDateTime.now(clockProvider.withZone(zoneId)), - ZonedDateTime.now(clockProvider.withZone(zoneId)), "Europe/Zurich", "http://localhost", "http://localhost", null, - "http://localhost", null, null, "CHF", BigDecimal.TEN, null, "42", organizationId, - ContentLanguage.ALL_LANGUAGES_IDENTIFIER, 0, PriceContainer.VatStatus.NONE, "1", Event.Status.PUBLIC); - } + PurchaseContext purchaseContext = getPurchaseContext(organizationId, eventId, subscriptionDescriptorId, principal, name); Organization organization = organizationRepository.getById(organizationId); - Optional<TemplateResource.ImageData> image = TemplateProcessor.extractImageModel(event, fileUploadManager); - Map<String, Object> model = name.prepareSampleModel(organization, event, image); - String renderedTemplate = templateManager.renderString(event, template.getFileAsString(), model, loc, name.getTemplateOutput()); + Optional<TemplateResource.ImageData> image = TemplateProcessor.extractImageModel(purchaseContext, fileUploadManager); + Map<String, Object> model = name.prepareSampleModel(organization, purchaseContext, image); + String renderedTemplate = templateManager.renderString(purchaseContext, template.getFileAsString(), model, loc, name.getTemplateOutput()); if(MediaType.TEXT_PLAIN_VALUE.equals(name.getRenderedContentType()) || TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE.equals(name.getRenderedContentType())) { response.addHeader("Content-Disposition", "attachment; filename="+name.name()+".txt"); response.setContentType(MediaType.TEXT_PLAIN_VALUE); @@ -148,7 +159,7 @@ public void previewTemplate(@PathVariable("name") TemplateResource name, @PathVa try (OutputStream os = response.getOutputStream()) { response.setContentType(MediaType.APPLICATION_PDF_VALUE); response.addHeader("Content-Disposition", "attachment; filename="+name.name()+".pdf"); - TemplateProcessor.renderToPdf(renderedTemplate, os, extensionManager, event); + TemplateProcessor.renderToPdf(renderedTemplate, os, extensionManager, purchaseContext); } } else { throw new IllegalStateException("cannot enter here!"); @@ -156,6 +167,79 @@ public void previewTemplate(@PathVariable("name") TemplateResource name, @PathVa } } + private PurchaseContext getPurchaseContext(int organizationId, + Integer eventId, + UUID subscriptionDescriptorId, + Principal principal, + TemplateResource templateResource) { + if (templateResource.getPurchaseContextType() == PurchaseContext.PurchaseContextType.event) { + return getEvent(organizationId, eventId, principal); + } + return getSubscriptionDescriptor(organizationId, subscriptionDescriptorId, principal); + } + + private SubscriptionDescriptor getSubscriptionDescriptor(int organizationId, UUID subscriptionDescriptorId, Principal principal) { + if (subscriptionDescriptorId != null) { + return subscriptionManager.getSubscriptionById(subscriptionDescriptorId).orElseThrow(); + } + + Function<String, Map<String, String>> contentProducer = prefix -> ContentLanguage.ALL_LANGUAGES.stream() + .map(cl -> Map.entry(cl.getLanguage(), cl.getDisplayLanguage() + " " + prefix)) + .reduce(new HashMap<String, String>(), (map, entry) -> { + map.put(entry.getKey(), entry.getValue()); + return map; + }, (map1, map2) -> { + map1.putAll(map2); + return map1; + }); + checkAccess(organizationId, principal); + var zoneId = ZoneId.of(TIMEZONE); + return new SubscriptionDescriptor(UUID.randomUUID(), + contentProducer.apply("title"), + contentProducer.apply("description"), + -1, + ZonedDateTime.now(clockProvider.withZone(zoneId)), + ZonedDateTime.now(clockProvider.withZone(zoneId)), + ZonedDateTime.now(clockProvider.withZone(zoneId)).plusDays(1), + 100, + new BigDecimal("7.7"), + PriceContainer.VatStatus.INCLUDED, + "CHF", + true, + organizationId, + 1, + SubscriptionDescriptor.SubscriptionValidityType.NOT_SET, + null, + -1, + ZonedDateTime.now(clockProvider.withZone(zoneId)), + ZonedDateTime.now(clockProvider.withZone(zoneId)).plusDays(1), + SubscriptionDescriptor.SubscriptionUsageType.ONCE_PER_EVENT, + "https://alf.io", + "https://alf.io", + null, + List.of(PaymentProxy.STRIPE.name()), + "42", + TIMEZONE, + true + ); + } + + private Event getEvent(Integer organizationId, Integer eventId, Principal principal) { + Event event; + if (eventId != null) { + checkAccess(organizationId, eventId, principal); + event = eventRepository.findById(eventId); + } else { + checkAccess(organizationId, principal); + var zoneId = ZoneId.of(TIMEZONE); + event = new Event(-1, Event.EventFormat.IN_PERSON, "TEST", "TEST", "TEST", "0", "0", ZonedDateTime.now(clockProvider.withZone(zoneId)), + ZonedDateTime.now(clockProvider.withZone(zoneId)), TIMEZONE, "http://localhost", "http://localhost", null, + "http://localhost", null, null, "CHF", BigDecimal.TEN, null, "42", organizationId, + ContentLanguage.ALL_LANGUAGES_IDENTIFIER, 0, PriceContainer.VatStatus.NONE, "1", Event.Status.PUBLIC); + } + return event; + } + //------------------ diff --git a/src/main/java/alfio/controller/api/admin/SerializablePair.java b/src/main/java/alfio/controller/api/admin/SerializablePair.java index 15d8a52d77..bbc7a19901 100644 --- a/src/main/java/alfio/controller/api/admin/SerializablePair.java +++ b/src/main/java/alfio/controller/api/admin/SerializablePair.java @@ -16,11 +16,13 @@ */ package alfio.controller.api.admin; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.experimental.Delegate; import org.apache.commons.lang3.tuple.Pair; public final class SerializablePair<L,R> { @Delegate + @JsonIgnore private final Pair<L, R> pair; private SerializablePair(Pair<L, R> pair) { diff --git a/src/main/java/alfio/controller/api/admin/SpecialPriceApiController.java b/src/main/java/alfio/controller/api/admin/SpecialPriceApiController.java index 397e31b8dc..32744f04e7 100644 --- a/src/main/java/alfio/controller/api/admin/SpecialPriceApiController.java +++ b/src/main/java/alfio/controller/api/admin/SpecialPriceApiController.java @@ -22,9 +22,10 @@ import alfio.model.modification.UploadBase64FileModification; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvException; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -42,9 +43,10 @@ @RestController @RequestMapping("/admin/api") -@Log4j2 public class SpecialPriceApiController { + private static final Logger log = LoggerFactory.getLogger(SpecialPriceApiController.class); + private final SpecialPriceManager specialPriceManager; @Autowired diff --git a/src/main/java/alfio/controller/api/admin/SubscriptionApiController.java b/src/main/java/alfio/controller/api/admin/SubscriptionApiController.java index 6268ee155a..785c3a25bf 100644 --- a/src/main/java/alfio/controller/api/admin/SubscriptionApiController.java +++ b/src/main/java/alfio/controller/api/admin/SubscriptionApiController.java @@ -29,6 +29,7 @@ import java.security.Principal; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @RestController @RequestMapping("/admin/api/organization/{organizationId}/subscription") @@ -37,7 +38,8 @@ public class SubscriptionApiController { private final SubscriptionManager subscriptionManager; private final UserManager userManager; - public SubscriptionApiController(SubscriptionManager subscriptionManager, UserManager userManager) { + public SubscriptionApiController(SubscriptionManager subscriptionManager, + UserManager userManager) { this.subscriptionManager = subscriptionManager; this.userManager = userManager; } @@ -75,10 +77,17 @@ ResponseEntity<SubscriptionDescriptorModification> getSingle(@PathVariable("orga ResponseEntity<UUID> create(@PathVariable("organizationId") int organizationId, @RequestBody SubscriptionDescriptorModification subscriptionDescriptor, Principal principal) { - if (organizationId == subscriptionDescriptor.getOrganizationId() && userManager.isOwnerOfOrganization(principal.getName(), subscriptionDescriptor.getOrganizationId())) { + + if (organizationId != subscriptionDescriptor.getOrganizationId() + || !userManager.isOwnerOfOrganization(principal.getName(), subscriptionDescriptor.getOrganizationId())) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + var result = SubscriptionDescriptorModification.validate(subscriptionDescriptor); + if (result.isSuccess()) { return ResponseEntity.of(subscriptionManager.createSubscriptionDescriptor(subscriptionDescriptor)); } else { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + return ResponseEntity.badRequest().build(); } } @@ -119,4 +128,43 @@ ResponseEntity<List<EventSubscriptionLink>> getLinkedEvents(@PathVariable("organ return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } } + + /** + * Deactivates a subscription descriptor and removes the link with events, if any. + * + * This will ensure that existing reservations made or ticket registered using this subscription won't have + * to be deleted. + * + * @param organizationId + * @param descriptorId + * @param principal + * @return + */ + @DeleteMapping("/{subscriptionId}") + ResponseEntity<Void> deactivate(@PathVariable("organizationId") int organizationId, + @PathVariable("subscriptionId") UUID descriptorId, + Principal principal) { + if(userManager.isOwnerOfOrganization(principal.getName(), organizationId)) { + return deactivateSubscriptionDescriptor(organizationId, descriptorId, subscriptionManager); + } + + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + public static ResponseEntity<Void> deactivateSubscriptionDescriptor(int organizationId, + UUID descriptorId, + SubscriptionManager subscriptionManager) { + var result = subscriptionManager.deactivateDescriptor(organizationId, descriptorId); + if (result.isSuccess()) { + return ResponseEntity.ok().build(); + } else { + return ResponseEntity.badRequest().build(); + } + } + + public static List<String> loadLinkedEvents(List<EventSubscriptionLink> eventSubscriptionLinks) { + return eventSubscriptionLinks.stream() + .map(EventSubscriptionLink::getEventShortName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/alfio/controller/api/admin/SystemApiKeyApiController.java b/src/main/java/alfio/controller/api/admin/SystemApiKeyApiController.java new file mode 100644 index 0000000000..428235e303 --- /dev/null +++ b/src/main/java/alfio/controller/api/admin/SystemApiKeyApiController.java @@ -0,0 +1,58 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.admin; + +import alfio.manager.system.ConfigurationManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/admin/api/system/api-key") +public class SystemApiKeyApiController { + private static final Logger log = LoggerFactory.getLogger(SystemApiKeyApiController.class); + private final ConfigurationManager configurationManager; + + public SystemApiKeyApiController(ConfigurationManager configurationManager) { + this.configurationManager = configurationManager; + } + + @GetMapping() + public ResponseEntity<String> retrieveApiKey() { + try { + return ResponseEntity.ok(configurationManager.retrieveSystemApiKey(false)); + } catch(RuntimeException e) { + log.error("Error while retrieving system API Key", e); + return ResponseEntity.internalServerError().build(); + } + } + + @PutMapping() + public ResponseEntity<String> rotateApiKey() { + try { + return ResponseEntity.ok(configurationManager.retrieveSystemApiKey(true)); + } catch(RuntimeException e) { + log.error("Error while rotating system API Key", e); + return ResponseEntity.internalServerError().build(); + } + } + +} diff --git a/src/main/java/alfio/controller/api/admin/UsersApiController.java b/src/main/java/alfio/controller/api/admin/UsersApiController.java index aee06ceb59..1de27f4c28 100644 --- a/src/main/java/alfio/controller/api/admin/UsersApiController.java +++ b/src/main/java/alfio/controller/api/admin/UsersApiController.java @@ -16,7 +16,7 @@ */ package alfio.controller.api.admin; -import alfio.config.authentication.AuthenticationConstants; +import alfio.config.authentication.support.AuthenticationConstants; import alfio.manager.system.ConfigurationManager; import alfio.manager.user.UserManager; import alfio.model.modification.OrganizationModification; @@ -28,15 +28,14 @@ import alfio.util.Json; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.*; @@ -55,14 +54,19 @@ @RestController @RequestMapping("/admin/api") -@Log4j2 -@AllArgsConstructor public class UsersApiController { + private static final Logger log = LoggerFactory.getLogger(UsersApiController.class); + private static final String OK = "OK"; private final UserManager userManager; private final ConfigurationManager configurationManager; + public UsersApiController(UserManager userManager, ConfigurationManager configurationManager) { + this.userManager = userManager; + this.configurationManager = configurationManager; + } + @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody @@ -78,19 +82,23 @@ public Collection<RoleDescriptor> getAllRoles(Principal principal) { /** * This endpoint is intended only for external use. If a user is registered as "sponsor", then the answer will be "SPONSOR", otherwise "OPERATOR". - * @return "SPONSOR" or "OPERATOR", depending on current user's privileges. + * @return "SPONSOR", "SUPERVISOR", or "OPERATOR", depending on current user's privileges. */ @GetMapping("/user-type") public String getLoggedUserType() { - return SecurityContextHolder.getContext() + var authorities = SecurityContextHolder.getContext() .getAuthentication() .getAuthorities() .stream() - .map(GrantedAuthority::getAuthority) - .map(s -> StringUtils.substringAfter(s, "ROLE_")) - .filter(AuthenticationConstants.SPONSOR::equals) - .findFirst() - .orElse(AuthenticationConstants.OPERATOR); + .map(ga -> StringUtils.substringAfter(ga.getAuthority(), "ROLE_")) + .collect(Collectors.toSet()); + if (authorities.contains(AuthenticationConstants.SPONSOR)) { + return AuthenticationConstants.SPONSOR; + } else if (authorities.contains(AuthenticationConstants.SUPERVISOR)) { + return AuthenticationConstants.SUPERVISOR; + } else { + return AuthenticationConstants.OPERATOR; + } } @GetMapping("/user/details") @@ -129,15 +137,15 @@ public ResponseEntity<String> bulkCreate(@RequestBody BulkApiKeyCreation request Optional<User> userOptional = userManager.findOptionalEnabledUserByUsername(principal.getName()) .filter(u -> userManager.isOwnerOfOrganization(u, request.organizationId)); if(userOptional.isPresent()) { - userManager.bulkInsertApiKeys(request.organizationId, request.role, request.descriptions); + userManager.bulkInsertApiKeys(request.organizationId, request.role, request.descriptions, principal); return ResponseEntity.ok("OK"); } return ResponseEntity.badRequest().build(); } @PostMapping("/organizations/new") - public String insertOrganization(@RequestBody OrganizationModification om) { - userManager.createOrganization(om); + public String insertOrganization(@RequestBody OrganizationModification om, Principal principal) { + userManager.createOrganization(om, principal); return OK; } @@ -172,7 +180,7 @@ public String editUser(@RequestBody UserModification userModification, Principal userManager.editUser(userModification.getId(), userModification.getOrganizationId(), userModification.getUsername(), userModification.getFirstName(), userModification.getLastName(), userModification.getEmailAddress(), userModification.getDescription(), - Role.valueOf(userModification.getRole()), principal.getName()); + Role.valueOf(userModification.getRole()), principal); return OK; } @@ -185,7 +193,7 @@ public UserWithPasswordAndQRCode insertUser(@RequestBody UserModification userMo userModification.getFirstName(), userModification.getLastName(), userModification.getEmailAddress(), requested, type == null ? User.Type.INTERNAL : type, - userModification.getValidToAsDateTime(), userModification.getDescription()); + userModification.getValidToAsDateTime(), userModification.getDescription(), principal); String qrCode = type != User.Type.API_KEY ? Base64.getEncoder().encodeToString(generateQRCode(userWithPassword, baseUrl)) : null; return new UserWithPasswordAndQRCode(userWithPassword, qrCode); } @@ -228,19 +236,19 @@ private static byte[] generateQRCode(UserWithPassword userWithPassword, String b @DeleteMapping("/users/{id}") public String deleteUser(@PathVariable("id") int userId, Principal principal) { - userManager.deleteUser(userId, principal.getName()); + userManager.deleteUser(userId, principal); return OK; } @PostMapping("/users/{id}/enable/{enable}") public String enableUser(@PathVariable("id") int userId, @PathVariable("enable")boolean enable, Principal principal) { - userManager.enable(userId, principal.getName(), enable); + userManager.enable(userId, enable, principal); return OK; } @GetMapping("/users/{id}") - public UserModification loadUser(@PathVariable("id") int userId) { - User user = userManager.findUser(userId); + public UserModification loadUser(@PathVariable("id") int userId, Principal principal) { + User user = userManager.findUser(userId, principal); List<Organization> userOrganizations = userManager.findUserOrganizations(user.getUsername()); return new UserModification(user.getId(), userOrganizations.get(0).getId(), userManager.getUserRole(user).name(), user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmailAddress(), @@ -259,19 +267,18 @@ public UserModification loadCurrentUser(Principal principal) { @PostMapping("/users/current/update-password") public ValidationResult updateCurrentUserPassword(@RequestBody PasswordModification passwordModification, Principal principal) { return userManager.validateNewPassword(principal.getName(), passwordModification.oldPassword, passwordModification.newPassword, passwordModification.newPasswordConfirm) - .ifSuccess(() -> userManager.updatePassword(principal.getName(), passwordModification.newPassword)); + .ifSuccess(() -> userManager.updateCurrentUserPassword(passwordModification.newPassword, principal)); } @PostMapping("/users/current/edit") public void updateCurrentUser(@RequestBody UserModification userModification, Principal principal) { - User user = userManager.findUserByUsername(principal.getName()); - userManager.updateUserContactInfo(user.getId(), userModification.getFirstName(), userModification.getLastName(), userModification.getEmailAddress()); + userManager.updateCurrentUserContactInfo(userModification.getFirstName(), userModification.getLastName(), userModification.getEmailAddress(), principal); } @PutMapping("/users/{id}/reset-password") - public UserWithPasswordAndQRCode resetPassword(@PathVariable("id") int userId, @RequestParam("baseUrl") String baseUrl) { - UserWithPassword userWithPassword = userManager.resetPassword(userId); + public UserWithPasswordAndQRCode resetPassword(@PathVariable("id") int userId, @RequestParam("baseUrl") String baseUrl, Principal principal) { + UserWithPassword userWithPassword = userManager.resetPassword(userId, principal); return new UserWithPasswordAndQRCode(userWithPassword, Base64.getEncoder().encodeToString(generateQRCode(userWithPassword, baseUrl))); } @@ -301,7 +308,7 @@ public String getDescription() { return role.getDescription(); } - public String getTarget() { return role.getTarget().name(); } + public List<String> getTarget() { return role.getTarget().stream().map(RoleTarget::name).collect(Collectors.toList()); } } private static final class PasswordModification { diff --git a/src/main/java/alfio/controller/api/admin/UtilsApiController.java b/src/main/java/alfio/controller/api/admin/UtilsApiController.java index 6a5dea5f2e..ef442b8fdb 100644 --- a/src/main/java/alfio/controller/api/admin/UtilsApiController.java +++ b/src/main/java/alfio/controller/api/admin/UtilsApiController.java @@ -22,9 +22,10 @@ import alfio.manager.EventNameManager; import alfio.util.MustacheCustomTag; import alfio.util.Wrappers; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; import org.joda.money.CurrencyUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; @@ -41,10 +42,11 @@ @RestController @RequestMapping("/admin/api/utils") -@Log4j2 public class UtilsApiController { - private static final List<String> CURRENCIES_BLACKLIST = Arrays.asList("USN", "USS", "CHE", "CHW"); + private static final Logger log = LoggerFactory.getLogger(UtilsApiController.class); + + private static final Set<String> CURRENCIES_BLOCKLIST = Set.of("USN", "USS", "CHE", "CHW"); private final EventNameManager eventNameManager; private final String version; private final Environment environment; @@ -94,9 +96,9 @@ public Map<String, Object> getApplicationInfo(Principal principal) { public List<CurrencyDescriptor> getCurrencies() { return CurrencyUnit.registeredCurrencies().stream() //we don't support pseudo currencies, as it is very unlikely that payment providers would support them - .filter(c -> !c.isPseudoCurrency() && !CURRENCIES_BLACKLIST.contains(c.getCode()) && Wrappers.optionally(() -> Currency.getInstance(c.getCode())).isPresent()) + .filter(c -> !c.isPseudoCurrency() && !CURRENCIES_BLOCKLIST.contains(c.getCode()) && Wrappers.optionally(() -> Currency.getInstance(c.getCode())).isPresent()) .map(c -> new CurrencyDescriptor(c.getCode(), c.toCurrency().getDisplayName(Locale.ENGLISH), c.getSymbol(Locale.ENGLISH), c.getDecimalPlaces())) - .collect(Collectors.toList()); + .toList(); } @GetMapping("/countriesForVat") diff --git a/src/main/java/alfio/controller/api/pass/PassKitApiController.java b/src/main/java/alfio/controller/api/pass/PassKitApiController.java index fb088fa90f..25c4fa05ef 100644 --- a/src/main/java/alfio/controller/api/pass/PassKitApiController.java +++ b/src/main/java/alfio/controller/api/pass/PassKitApiController.java @@ -19,9 +19,9 @@ import alfio.manager.PassKitManager; import alfio.model.EventAndOrganizationId; import alfio.model.Ticket; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -34,12 +34,16 @@ */ @RestController @RequestMapping("/api/pass/event/{eventName}/v1") -@Log4j2 -@RequiredArgsConstructor public class PassKitApiController { + private static final Logger log = LoggerFactory.getLogger(PassKitApiController.class); + private final PassKitManager passKitManager; + public PassKitApiController(PassKitManager passKitManager) { + this.passKitManager = passKitManager; + } + @GetMapping("/version/passes/{passTypeIdentifier}/{serialNumber}") public void getLatestVersion(@PathVariable("eventName") String eventName, @@ -52,13 +56,36 @@ public void getLatestVersion(@PathVariable("eventName") String eventName, response.sendError(HttpServletResponse.SC_NOT_FOUND); } else { Pair<EventAndOrganizationId, Ticket> pair = validationResult.get(); - try (var os = response.getOutputStream()) { - response.setContentType("application/vnd.apple.pkpass"); - passKitManager.writePass(pair.getRight(), pair.getLeft(), os); - } catch (Exception e) { - log.warn("Error during pass generation", e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + writePassResponse(response, pair.getLeft(), pair.getRight(), false); + } + } + + @GetMapping("/version/passes/{ticketUuid}") + public void downloadPassForTicket(@PathVariable("eventName") String eventName, + @PathVariable("ticketUuid") String ticketUuid, + HttpServletResponse response) throws IOException { + var ticketAndEventData = passKitManager.retrieveTicketDetails(eventName, ticketUuid); + if (ticketAndEventData.isPresent()) { + var pair = ticketAndEventData.get(); + writePassResponse(response, pair.getKey(), pair.getValue(), true); + } else { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + + private void writePassResponse(HttpServletResponse response, + EventAndOrganizationId eventAndOrganizationId, + Ticket ticket, + boolean addFilename) throws IOException { + try (var os = response.getOutputStream()) { + response.setContentType("application/vnd.apple.pkpass"); + if (addFilename) { + response.setHeader("Content-Disposition", "attachment; filename=Passbook-"+ticket.getUuid().substring(0, 8)+".pkpass"); } + passKitManager.writePass(ticket, eventAndOrganizationId, os); + } catch (Exception e) { + log.warn("Error during pass generation", e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } diff --git a/src/main/java/alfio/controller/api/support/AdditionalField.java b/src/main/java/alfio/controller/api/support/AdditionalField.java new file mode 100644 index 0000000000..a8e4b46de2 --- /dev/null +++ b/src/main/java/alfio/controller/api/support/AdditionalField.java @@ -0,0 +1,34 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.support; + +import java.util.List; +import java.util.Map; + + +public record AdditionalField(String name, + String value, + String type, + boolean required, + boolean editable, + Integer minLength, + Integer maxLength, + List<String> restrictedValues, + List<Field> fields, + boolean beforeStandardFields, + Map<String, Description> description) { +} diff --git a/src/main/java/alfio/controller/api/support/BookingInfoTicket.java b/src/main/java/alfio/controller/api/support/BookingInfoTicket.java new file mode 100644 index 0000000000..c18db9af69 --- /dev/null +++ b/src/main/java/alfio/controller/api/support/BookingInfoTicket.java @@ -0,0 +1,135 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.support; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class BookingInfoTicket { + private final String uuid; + private final String firstName; + private final String lastName; + private final String email; + private final String fullName; + private final String userLanguage; + private final boolean assigned; + private final boolean locked; + private final boolean acquired; + private final boolean cancellationEnabled; + private final boolean sendMailEnabled; + private final boolean downloadEnabled; + private final List<AdditionalField> ticketFieldConfiguration; + private final Map<String, String> formattedOnlineCheckInDate; + private final boolean onlineEventStarted; + + public BookingInfoTicket(String uuid, + String firstName, + String lastName, + String email, + String fullName, + String userLanguage, + boolean assigned, + boolean locked, + boolean acquired, + boolean cancellationEnabled, + boolean sendMailEnabled, + boolean downloadEnabled, + List<AdditionalField> ticketFieldConfiguration, + Map<String, String> formattedOnlineCheckInDate, + boolean onlineEventStarted) { + this.uuid = uuid; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.fullName = fullName; + this.userLanguage = userLanguage; + this.assigned = assigned; + this.locked = locked; + this.acquired = acquired; + this.cancellationEnabled = cancellationEnabled; + this.sendMailEnabled = sendMailEnabled; + this.downloadEnabled = downloadEnabled; + this.ticketFieldConfiguration = ticketFieldConfiguration; + this.formattedOnlineCheckInDate = formattedOnlineCheckInDate; + this.onlineEventStarted = onlineEventStarted; + } + + public String getEmail() { + return email; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getUuid() { + return uuid; + } + + public String getFullName() { + return fullName; + } + + public boolean isAssigned() { + return assigned; + } + + public boolean isAcquired() { + return acquired; + } + + public String getUserLanguage() { + return userLanguage; + } + + public boolean isLocked() { + return locked; + } + + public boolean isCancellationEnabled() { + return cancellationEnabled; + } + + public List<AdditionalField> getTicketFieldConfigurationBeforeStandard() { + return ticketFieldConfiguration.stream().filter(AdditionalField::beforeStandardFields).collect(Collectors.toList()); + } + + public List<AdditionalField> getTicketFieldConfigurationAfterStandard() { + return ticketFieldConfiguration.stream().filter(tv -> !tv.beforeStandardFields()).collect(Collectors.toList()); + } + + public Map<String, String> getFormattedOnlineCheckInDate() { + return formattedOnlineCheckInDate; + } + + public boolean isOnlineEventStarted() { + return onlineEventStarted; + } + + public boolean isSendMailEnabled() { + return sendMailEnabled; + } + + public boolean isDownloadEnabled() { + return downloadEnabled; + } +} diff --git a/src/main/java/alfio/controller/api/v2/user/support/BookingInfoTicketLoader.java b/src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java similarity index 66% rename from src/main/java/alfio/controller/api/v2/user/support/BookingInfoTicketLoader.java rename to src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java index e2073c8c73..8222e18bba 100644 --- a/src/main/java/alfio/controller/api/v2/user/support/BookingInfoTicketLoader.java +++ b/src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java @@ -14,10 +14,8 @@ * You should have received a copy of the GNU General Public License * along with alf.io. If not, see <http://www.gnu.org/licenses/>. */ -package alfio.controller.api.v2.user.support; +package alfio.controller.api.support; -import alfio.controller.api.support.TicketHelper; -import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.support.Formatters; import alfio.manager.EventManager; import alfio.manager.TicketReservationManager; @@ -30,7 +28,6 @@ import alfio.util.ClockProvider; import alfio.util.EventUtil; import alfio.util.Validator; -import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import java.util.*; @@ -40,7 +37,6 @@ import static alfio.model.system.ConfigurationKeys.*; @Component -@AllArgsConstructor public class BookingInfoTicketLoader { private final EventManager eventManager; @@ -52,8 +48,26 @@ public class BookingInfoTicketLoader { private final MessageSourceManager messageSourceManager; private final ClockProvider clockProvider; + public BookingInfoTicketLoader(EventManager eventManager, + ConfigurationManager configurationManager, + TicketFieldRepository ticketFieldRepository, + TicketHelper ticketHelper, + AdditionalServiceItemRepository additionalServiceItemRepository, + TicketReservationManager ticketReservationManager, + MessageSourceManager messageSourceManager, + ClockProvider clockProvider) { + this.eventManager = eventManager; + this.configurationManager = configurationManager; + this.ticketFieldRepository = ticketFieldRepository; + this.ticketHelper = ticketHelper; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.ticketReservationManager = ticketReservationManager; + this.messageSourceManager = messageSourceManager; + this.clockProvider = clockProvider; + } + - public ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event) { + public BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event) { var descriptionsByTicketFieldId = ticketFieldRepository.findDescriptions(event.getShortName()) .stream() .collect(Collectors.groupingBy(TicketFieldDescription::getTicketFieldConfigurationId)); @@ -69,7 +83,7 @@ public ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, Even var eventConfiguration = eventManager.getMetadataForEvent(event).getOnlineConfiguration(); var ticketCategoryConfiguration = eventManager.getMetadataForCategory(event, ticket.getCategoryId()).getOnlineConfiguration(); var checkInDate = EventUtil.firstMatchingCallLink(event.getZoneId(), ticketCategoryConfiguration, eventConfiguration) - .map(link -> link.getValidFrom().atZone(event.getZoneId())) + .map(link -> link.validFrom().atZone(event.getZoneId())) .orElse(event.getBegin()); formattedDates = Formatters.getFormattedDate(event, checkInDate, "common.ticket-category.date-format", messageSourceManager.getMessageSourceFor(event)); @@ -86,17 +100,18 @@ public ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, Even onlineEventStarted); } - public ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket t, - boolean hasPaidSupplement, - Event event, - Validator.TicketFieldsFilterer ticketFieldsFilterer, - Map<Integer, List<TicketFieldDescription>> descriptionsByTicketFieldId, - Map<Integer, List<TicketFieldValue>> valuesByTicketIds, - Map<String, String> formattedOnlineCheckInDate, - boolean onlineEventStarted) { + public BookingInfoTicket toBookingInfoTicket(Ticket t, + boolean hasPaidSupplement, + Event event, + Validator.TicketFieldsFilterer ticketFieldsFilterer, + Map<Integer, List<TicketFieldDescription>> descriptionsByTicketFieldId, + Map<Integer, List<TicketFieldValue>> valuesByTicketIds, + Map<String, String> formattedOnlineCheckInDate, + boolean onlineEventStarted) { // TODO: n+1, should be cleaned up! see TicketDecorator.getCancellationEnabled var configuration = configurationManager.getFor(EnumSet.of(ALLOW_FREE_TICKETS_CANCELLATION, SEND_TICKETS_AUTOMATICALLY, ALLOW_TICKET_DOWNLOAD), ConfigurationLevel.ticketCategory(event, t.getCategoryId())); boolean cancellationEnabled = t.getFinalPriceCts() == 0 && + !event.expired() && (!hasPaidSupplement && configuration.get(ALLOW_FREE_TICKETS_CANCELLATION).getValueAsBooleanOrDefault()) && // freeCancellationEnabled eventManager.checkTicketCancellationPrerequisites().apply(t); // cancellationPrerequisitesMet // @@ -118,15 +133,15 @@ public Validator.TicketFieldsFilterer getTicketFieldsFilterer(String reservation ticketReservationManager.findFirstInReservation(reservationId)); } - private static ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, - boolean cancellationEnabled, - boolean sendMailEnabled, - boolean downloadEnabled, - List<TicketFieldConfiguration> ticketFields, - Map<Integer, List<TicketFieldDescription>> descriptionsByTicketFieldId, - List<TicketFieldValue> ticketFieldValues, - Map<String, String> formattedOnlineCheckInDate, - boolean onlineEventStarted) { + private static BookingInfoTicket toBookingInfoTicket(Ticket ticket, + boolean cancellationEnabled, + boolean sendMailEnabled, + boolean downloadEnabled, + List<TicketFieldConfiguration> ticketFields, + Map<Integer, List<TicketFieldDescription>> descriptionsByTicketFieldId, + List<TicketFieldValue> ticketFieldValues, + Map<String, String> formattedOnlineCheckInDate, + boolean onlineEventStarted) { var valueById = ticketFieldValues.stream().collect(Collectors.toMap(TicketFieldValue::getTicketFieldConfigurationId, Function.identity())); @@ -142,7 +157,7 @@ private static ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket tick return toAdditionalField(t, descs); }).collect(Collectors.toList()); - return new ReservationInfo.BookingInfoTicket(ticket.getUuid(), + return new BookingInfoTicket(ticket.getUuid(), ticket.getFirstName(), ticket.getLastName(), ticket.getEmail(), @@ -159,15 +174,15 @@ private static ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket tick onlineEventStarted); } - private static ReservationInfo.AdditionalField toAdditionalField(TicketFieldConfigurationDescriptionAndValue t, Map<String, ReservationInfo.Description> description) { - var fields = t.getFields().stream().map(f -> new ReservationInfo.Field(f.getFieldIndex(), f.getFieldValue())).collect(Collectors.toList()); - return new ReservationInfo.AdditionalField(t.getName(), t.getValue(), t.getType(), t.isRequired(), t.isEditable(), + private static AdditionalField toAdditionalField(TicketFieldConfigurationDescriptionAndValue t, Map<String, Description> description) { + var fields = t.getFields().stream().map(f -> new Field(f.getFieldIndex(), f.getFieldValue())).collect(Collectors.toList()); + return new AdditionalField(t.getName(), t.getValue(), t.getType(), t.isRequired(), t.isEditable(), t.getMinLength(), t.getMaxLength(), t.getRestrictedValues(), fields, t.isBeforeStandardFields(), description); } - private static Map<String, ReservationInfo.Description> fromFieldDescriptions(List<TicketFieldDescription> descs) { + private static Map<String, Description> fromFieldDescriptions(List<TicketFieldDescription> descs) { return descs.stream().collect(Collectors.toMap(TicketFieldDescription::getLocale, - d -> new ReservationInfo.Description(d.getLabelDescription(), d.getPlaceholderDescription(), d.getRestrictedValuesDescription()))); + d -> new Description(d.getLabelDescription(), d.getPlaceholderDescription(), d.getRestrictedValuesDescription()))); } } diff --git a/src/main/java/alfio/controller/api/support/CspReportApiController.java b/src/main/java/alfio/controller/api/support/CspReportApiController.java index eb629c1061..4bdfb037f0 100644 --- a/src/main/java/alfio/controller/api/support/CspReportApiController.java +++ b/src/main/java/alfio/controller/api/support/CspReportApiController.java @@ -18,8 +18,8 @@ import alfio.manager.system.ConfigurationLevel; import alfio.manager.system.ConfigurationManager; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -33,13 +33,17 @@ @RestController -@Log4j2 -@AllArgsConstructor public class CspReportApiController { + private static final Logger log = LoggerFactory.getLogger(CspReportApiController.class); + private final ConfigurationManager configurationManager; + public CspReportApiController(ConfigurationManager configurationManager) { + this.configurationManager = configurationManager; + } + @PostMapping("/report-csp-violation") public boolean logCspViolation(HttpServletRequest request) throws IOException { diff --git a/src/main/java/alfio/controller/api/support/CurrencyDescriptor.java b/src/main/java/alfio/controller/api/support/CurrencyDescriptor.java index 4cc44b2afe..579a6b5e62 100644 --- a/src/main/java/alfio/controller/api/support/CurrencyDescriptor.java +++ b/src/main/java/alfio/controller/api/support/CurrencyDescriptor.java @@ -17,13 +17,18 @@ package alfio.controller.api.support; import lombok.Getter; -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor @Getter public class CurrencyDescriptor { private final String code; private final String name; private final String symbol; private final int fractionDigits; + + public CurrencyDescriptor(String code, String name, String symbol, int fractionDigits) { + this.code = code; + this.name = name; + this.symbol = symbol; + this.fractionDigits = fractionDigits; + } } diff --git a/src/main/java/alfio/manager/payment/stripe/StripeConnectResult.java b/src/main/java/alfio/controller/api/support/Description.java similarity index 76% rename from src/main/java/alfio/manager/payment/stripe/StripeConnectResult.java rename to src/main/java/alfio/controller/api/support/Description.java index c1b1de7690..db1ea3309e 100644 --- a/src/main/java/alfio/manager/payment/stripe/StripeConnectResult.java +++ b/src/main/java/alfio/controller/api/support/Description.java @@ -14,13 +14,9 @@ * You should have received a copy of the GNU General Public License * along with alf.io. If not, see <http://www.gnu.org/licenses/>. */ -package alfio.manager.payment.stripe; +package alfio.controller.api.support; -import lombok.Data; +import java.util.Map; -@Data -public class StripeConnectResult { - private final String accountId; - private final boolean success; - private final String errorMessage; +public record Description(String label, String placeholder, Map<String, String> restrictedValuesDescription) { } diff --git a/src/main/java/alfio/controller/api/support/EventListItem.java b/src/main/java/alfio/controller/api/support/EventListItem.java index 9149dec469..c2f7fce031 100644 --- a/src/main/java/alfio/controller/api/support/EventListItem.java +++ b/src/main/java/alfio/controller/api/support/EventListItem.java @@ -26,6 +26,7 @@ public class EventListItem { + private static final int API_VERSION = 204; protected final Event event; private final String requestContextPath; private final List<EventDescription> eventDescriptions; @@ -85,7 +86,7 @@ public String getTimeZone() { } public int getApiVersion() { - return 17; + return API_VERSION; } } diff --git a/src/main/java/alfio/controller/api/support/Field.java b/src/main/java/alfio/controller/api/support/Field.java new file mode 100644 index 0000000000..551e3cdd84 --- /dev/null +++ b/src/main/java/alfio/controller/api/support/Field.java @@ -0,0 +1,20 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.support; + +public record Field(int fieldIndex, String fieldValue) { +} diff --git a/src/main/java/alfio/controller/api/support/PageAndContent.java b/src/main/java/alfio/controller/api/support/PageAndContent.java index 02c3928d42..01cd65724b 100644 --- a/src/main/java/alfio/controller/api/support/PageAndContent.java +++ b/src/main/java/alfio/controller/api/support/PageAndContent.java @@ -16,12 +16,20 @@ */ package alfio.controller.api.support; -import lombok.AllArgsConstructor; -import lombok.Data; - -@Data -@AllArgsConstructor public class PageAndContent<T> { private final T left; private final Integer right; + + public PageAndContent(T left, Integer right) { + this.left = left; + this.right = right; + } + + public Integer getRight() { + return right; + } + + public T getLeft() { + return left; + } } diff --git a/src/main/java/alfio/controller/api/support/PublicEventDescription.java b/src/main/java/alfio/controller/api/support/PublicEventDescription.java index 191ada497c..0e30c72c52 100644 --- a/src/main/java/alfio/controller/api/support/PublicEventDescription.java +++ b/src/main/java/alfio/controller/api/support/PublicEventDescription.java @@ -17,11 +17,10 @@ package alfio.controller.api.support; import alfio.model.EventDescription; -import lombok.experimental.Delegate; import org.apache.commons.lang3.StringUtils; public class PublicEventDescription { - @Delegate(excludes = {EventDescriptionExcludes.class}) + private final EventDescription eventDescription; PublicEventDescription(EventDescription eventDescription) { @@ -29,15 +28,19 @@ public class PublicEventDescription { } public String getShortDescription() { - return StringUtils.abbreviate(getDescription(), 100); + return StringUtils.abbreviate(eventDescription.description(), 100); + } + + public String getLocale() { + return eventDescription.locale(); + } + + public String getDescription() { + return eventDescription.description(); } static PublicEventDescription fromEventDescription(EventDescription eventDescription) { return new PublicEventDescription(eventDescription); } - interface EventDescriptionExcludes { - int getEventId(); - EventDescription.EventDescriptionType getEventDescriptionType(); - } } diff --git a/src/main/java/alfio/controller/api/support/TicketHelper.java b/src/main/java/alfio/controller/api/support/TicketHelper.java index daa40220d9..c2f2f9b2ac 100644 --- a/src/main/java/alfio/controller/api/support/TicketHelper.java +++ b/src/main/java/alfio/controller/api/support/TicketHelper.java @@ -26,12 +26,8 @@ import alfio.model.user.Organization; import alfio.repository.*; import alfio.repository.user.OrganizationRepository; -import alfio.util.EventUtil; -import alfio.util.LocaleUtil; -import alfio.util.TemplateManager; -import alfio.util.Validator; +import alfio.util.*; import alfio.util.Validator.AdvancedTicketAssignmentValidator; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -44,10 +40,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.stream.Collectors.*; - @Component -@AllArgsConstructor public class TicketHelper { private static final Set<TicketReservation.TicketReservationStatus> PENDING_RESERVATION_STATUSES = EnumSet.of(TicketReservation.TicketReservationStatus.PENDING, TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT); @@ -63,8 +56,30 @@ public class TicketHelper { private final GroupManager groupManager; private final ConfigurationManager configurationManager; private final ExtensionManager extensionManager; - private final TicketReservationRepository ticketReservationRepository; - private final EventManager eventManager; + + public TicketHelper(TicketReservationManager ticketReservationManager, + OrganizationRepository organizationRepository, + TicketCategoryRepository ticketCategoryRepository, + TicketRepository ticketRepository, + TemplateManager templateManager, + TicketFieldRepository ticketFieldRepository, + AdditionalServiceItemRepository additionalServiceItemRepository, + EuVatChecker vatChecker, + GroupManager groupManager, + ConfigurationManager configurationManager, + ExtensionManager extensionManager) { + this.ticketReservationManager = ticketReservationManager; + this.organizationRepository = organizationRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketRepository = ticketRepository; + this.templateManager = templateManager; + this.ticketFieldRepository = ticketFieldRepository; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.vatChecker = vatChecker; + this.groupManager = groupManager; + this.configurationManager = configurationManager; + this.extensionManager = extensionManager; + } public Function<Ticket, List<TicketFieldConfigurationDescriptionAndValue>> buildRetrieveFieldValuesFunction() { @@ -217,7 +232,7 @@ private void updateTicketOwner(UpdateTicketOwnerForm updateTicketOwner, Locale f private PartialTicketTextGenerator getOwnerChangeTextBuilder(Locale ticketLanguage, Ticket t, Event event) { Organization organization = organizationRepository.getById(event.getOrganizationId()); - String ticketUrl = ticketReservationManager.ticketUpdateUrl(event, t.getUuid()); + String ticketUrl = ReservationUtil.ticketUpdateUrl(event, t, configurationManager); return TemplateProcessor.buildEmailForOwnerChange(event, t, organization, ticketUrl, templateManager, ticketLanguage); } diff --git a/src/main/java/alfio/controller/api/support/WebhookApiController.java b/src/main/java/alfio/controller/api/support/WebhookApiController.java index 1bba048b73..1b497789f3 100644 --- a/src/main/java/alfio/controller/api/support/WebhookApiController.java +++ b/src/main/java/alfio/controller/api/support/WebhookApiController.java @@ -18,8 +18,6 @@ import alfio.manager.payment.StripeCreditCardManager; import alfio.util.RequestUtils; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -28,14 +26,16 @@ @RestController @RequestMapping("/api/webhook") -@AllArgsConstructor -@Log4j2 public class WebhookApiController { //private final MollieManager mollieManager; //private final TicketReservationManager ticketReservationManager; private final StripeCreditCardManager stripeCreditCardManager; + public WebhookApiController(StripeCreditCardManager stripeCreditCardManager) { + this.stripeCreditCardManager = stripeCreditCardManager; + } + @PostMapping("/mollie/event/{eventName}/reservation/{reservationId}") public void handleMollie(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId) { // mollieManager.handleWebhook(eventName, reservationId, null); diff --git a/src/main/java/alfio/controller/api/v1/AttendeeApiController.java b/src/main/java/alfio/controller/api/v1/AttendeeApiController.java index 50db29d3a0..1e81f2f19d 100644 --- a/src/main/java/alfio/controller/api/v1/AttendeeApiController.java +++ b/src/main/java/alfio/controller/api/v1/AttendeeApiController.java @@ -27,9 +27,9 @@ import alfio.util.Wrappers; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; @@ -48,9 +48,11 @@ @RestController @RequestMapping("/api/attendees") -@Log4j2 public class AttendeeApiController { + public static final String ALFIO_OPERATOR_HEADER = "Alfio-Operator"; + private static final Logger log = LoggerFactory.getLogger(AttendeeApiController.class); + private final AttendeeManager attendeeManager; @Autowired @@ -72,15 +74,19 @@ public ResponseEntity<String> handleGenericException(RuntimeException e) { @PostMapping("/sponsor-scan") - public ResponseEntity<TicketAndCheckInResult> scanBadge(@RequestBody SponsorScanRequest request, Principal principal) { - return ResponseEntity.ok(attendeeManager.registerSponsorScan(request.eventName, request.ticketIdentifier, request.notes, request.leadStatus, principal.getName())); + public ResponseEntity<TicketAndCheckInResult> scanBadge(@RequestBody SponsorScanRequest request, + Principal principal, + @RequestHeader(name = ALFIO_OPERATOR_HEADER, required = false) String operator) { + return ResponseEntity.ok(attendeeManager.registerSponsorScan(request.eventName, request.ticketIdentifier, request.notes, request.leadStatus, principal.getName(), operator)); } @PostMapping("/sponsor-scan/bulk") - public ResponseEntity<List<TicketAndCheckInResult>> scanBadges(@RequestBody List<SponsorScanRequest> requests, Principal principal) { + public ResponseEntity<List<TicketAndCheckInResult>> scanBadges(@RequestBody List<SponsorScanRequest> requests, + Principal principal, + @RequestHeader(name = ALFIO_OPERATOR_HEADER, required = false) String operator) { String username = principal.getName(); return ResponseEntity.ok(requests.stream() - .map(request -> attendeeManager.registerSponsorScan(request.eventName, request.ticketIdentifier, request.notes, request.leadStatus, username)) + .map(request -> attendeeManager.registerSponsorScan(request.eventName, request.ticketIdentifier, request.notes, request.leadStatus, username, operator)) .collect(Collectors.toList())); } @@ -119,13 +125,28 @@ private static <T> ResponseEntity<T> notFound() { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - @Getter public static class SponsorScanRequest { private final String eventName; private final String ticketIdentifier; private final String notes; private final SponsorScan.LeadStatus leadStatus; + public String getEventName() { + return eventName; + } + + public String getTicketIdentifier() { + return ticketIdentifier; + } + + public String getNotes() { + return notes; + } + + public SponsorScan.LeadStatus getLeadStatus() { + return leadStatus; + } + @JsonCreator public SponsorScanRequest(@JsonProperty("eventName") String eventName, @JsonProperty("ticketIdentifier") String ticketIdentifier, diff --git a/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java b/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java index 145c5ce1db..7971e6b28b 100644 --- a/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java +++ b/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java @@ -17,13 +17,20 @@ package alfio.controller.api.v1.admin; import alfio.extension.ExtensionService; +import alfio.job.executor.AssignTicketToSubscriberJobExecutor; import alfio.manager.*; +import alfio.manager.system.AdminJobManager; import alfio.manager.system.ConfigurationLevel; import alfio.manager.system.ConfigurationManager; import alfio.manager.user.UserManager; -import alfio.model.*; +import alfio.model.Event; +import alfio.model.EventWithAdditionalInfo; +import alfio.model.ExtensionSupport; import alfio.model.ExtensionSupport.ExtensionMetadataValue; +import alfio.model.PromoCodeDiscount; +import alfio.model.api.v1.admin.CheckInLogEntry; import alfio.model.api.v1.admin.EventCreationRequest; +import alfio.model.api.v1.admin.LinkedSubscriptions; import alfio.model.group.Group; import alfio.model.modification.EventModification; import alfio.model.modification.LinkedGroupModification; @@ -34,10 +41,10 @@ import alfio.model.user.Organization; import alfio.repository.ExtensionRepository; import alfio.util.Json; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BeanPropertyBindingResult; @@ -46,23 +53,23 @@ import java.security.Principal; import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static alfio.controller.api.admin.EventApiController.validateEvent; +import static alfio.manager.system.AdminJobExecutor.JobName.ASSIGN_TICKETS_TO_SUBSCRIBERS; +import static alfio.model.api.v1.admin.EventCreationRequest.findExistingCategory; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @RestController @RequestMapping("/api/v1/admin/event") -@AllArgsConstructor -@Log4j2 public class EventApiV1Controller { + private static final Logger log = LoggerFactory.getLogger(EventApiV1Controller.class); + private final EventManager eventManager; private final EventNameManager eventNameManager; private final FileUploadManager fileUploadManager; @@ -73,6 +80,34 @@ public class EventApiV1Controller { private final ExtensionService extensionService; private final ExtensionRepository extensionRepository; private final ConfigurationManager configurationManager; + private final AdminJobManager adminJobManager; + private final CheckInManager checkInManager; + + public EventApiV1Controller(EventManager eventManager, + EventNameManager eventNameManager, + FileUploadManager fileUploadManager, + FileDownloadManager fileDownloadManager, + UserManager userManager, + EventStatisticsManager eventStatisticsManager, + GroupManager groupManager, + ExtensionService extensionService, + ExtensionRepository extensionRepository, + ConfigurationManager configurationManager, + AdminJobManager adminJobManager, + CheckInManager checkInManager) { + this.eventManager = eventManager; + this.eventNameManager = eventNameManager; + this.fileUploadManager = fileUploadManager; + this.fileDownloadManager = fileDownloadManager; + this.userManager = userManager; + this.eventStatisticsManager = eventStatisticsManager; + this.groupManager = groupManager; + this.extensionService = extensionService; + this.extensionRepository = extensionRepository; + this.configurationManager = configurationManager; + this.adminJobManager = adminJobManager; + this.checkInManager = checkInManager; + } @PostMapping("/create") @Transactional @@ -90,6 +125,7 @@ public ResponseEntity<String> create(@RequestBody EventCreationRequest request, .checkPrecondition(() -> isNotBlank(request.getImageUrl()), ErrorCode.custom("invalid.imageUrl", "Invalid Image URL")) .checkPrecondition(() -> isNotBlank(request.getTimezone()), ErrorCode.custom("invalid.timezone", "Invalid Timezone")) .checkPrecondition(() -> isNotBlank(imageRef), ErrorCode.custom("invalid.image", "Image is either missing or too big (max 200kb)")) + .checkPrecondition(() -> validateCategoriesSalesPeriod(request), ErrorCode.custom("invalid.categories", "Ticket categories: sales period not compatible with event dates")) .checkPrecondition(() -> { EventModification eventModification = request.toEventModification(organization, eventNameManager::generateShortName, imageRef); errorsContainer.set(new BeanPropertyBindingResult(eventModification, "event")); @@ -102,7 +138,7 @@ public ResponseEntity<String> create(@RequestBody EventCreationRequest request, }, ErrorCode.lazy(() -> toErrorCode(errorsContainer.get()))) //TODO all location validation //TODO language validation, for all the description the same languages - .build(() -> insertEvent(request, user, imageRef).map(Event::getShortName).orElseThrow(IllegalStateException::new)); + .build(() -> insertEvent(request, user, imageRef).orElseThrow(IllegalStateException::new)); if(result.isSuccess()) { return ResponseEntity.ok(result.getData()); @@ -112,6 +148,12 @@ public ResponseEntity<String> create(@RequestBody EventCreationRequest request, } + private boolean validateCategoriesSalesPeriod(EventCreationRequest request) { + var eventEnd = request.getEndDate(); + return request.getTickets().getCategories().stream() + .allMatch(tc -> tc.getStartSellingDate().isBefore(tc.getEndSellingDate()) && tc.getEndSellingDate().isBefore(eventEnd)); + } + private ErrorCode toErrorCode(Errors errors) { return errors.getFieldErrors().stream() .map(e -> ErrorCode.custom(e.getField(), e.getCode())) @@ -152,7 +194,7 @@ public ResponseEntity<String> update(@PathVariable("slug") String slug, @Request String imageRef = fetchImage(request.getImageUrl()); Result<String> result = new Result.Builder<String>() - .build(() -> updateEvent(slug, request, user, imageRef).map(Event::getShortName).get()); + .build(() -> updateEvent(slug, request, user, imageRef).map(Event::getShortName).orElseThrow()); if(result.isSuccess()) { return ResponseEntity.ok(result.getData()); @@ -161,6 +203,57 @@ public ResponseEntity<String> update(@PathVariable("slug") String slug, @Request } } + @GetMapping("/{slug}/subscriptions") + public ResponseEntity<LinkedSubscriptions> getLinkedSubscriptions(@PathVariable("slug") String slug, Principal user) { + + var subscriptionIdsOptional = eventManager.getOptionalEventAndOrganizationIdByName(slug, user.getName()) + .map(event -> retrieveLinkedSubscriptionsForEvent(slug, event.getId(), event.getOrganizationId())); + + return ResponseEntity.of(subscriptionIdsOptional); + } + + @PutMapping("/{slug}/subscriptions") + public ResponseEntity<LinkedSubscriptions> updateLinkedSubscriptions(@PathVariable("slug") String slug, + @RequestBody List<UUID> subscriptions, + Principal user) { + var eventOptional = eventManager.getOptionalByName(slug, user.getName()); + if (eventOptional.isEmpty()) { + return ResponseEntity.badRequest().build(); + } + var eventAndOrgId = eventOptional.get(); + eventManager.updateLinkedSubscriptions(subscriptions, eventAndOrgId.getId(), eventAndOrgId.getOrganizationId()); + return ResponseEntity.ok(retrieveLinkedSubscriptionsForEvent(slug, eventAndOrgId.getId(), eventAndOrgId.getOrganizationId())); + } + + @PostMapping("/{slug}/generate-subscribers-tickets") + public ResponseEntity<Boolean> generateTicketsForSubscribers(@PathVariable("slug") String slug, + Principal user) { + return ResponseEntity.of(eventManager.getOptionalEventAndOrganizationIdByName(slug, user.getName()).map(eventAndOrganizationId -> { + Map<String, Object> params = Map.of( + AssignTicketToSubscriberJobExecutor.EVENT_ID, eventAndOrganizationId.getId(), + AssignTicketToSubscriberJobExecutor.ORGANIZATION_ID, eventAndOrganizationId.getOrganizationId(), + AssignTicketToSubscriberJobExecutor.FORCE_GENERATION, true + ); + return adminJobManager.scheduleExecution(ASSIGN_TICKETS_TO_SUBSCRIBERS, params); + })); + } + + @GetMapping("/{slug}/check-in-log") + public ResponseEntity<List<CheckInLogEntry>> checkInLog(@PathVariable("slug") String slug, + Principal user) { + try { + return ResponseEntity.ok(checkInManager.retrieveLogEntries(slug, user.getName())); + } catch (Exception ex) { + log.error("Error while loading check-in log entries", ex); + return ResponseEntity.internalServerError().build(); + } + } + + private LinkedSubscriptions retrieveLinkedSubscriptionsForEvent(String slug, int id, int organizationId) { + var subscriptionIds = eventManager.getLinkedSubscriptionIds(id, organizationId); + return new LinkedSubscriptions(slug, subscriptionIds); + } + private Optional<Event> updateEvent(String slug, EventCreationRequest request, Principal user, String imageRef) { Organization organization = userManager.findUserOrganizations(user.getName()).get(0); EventWithAdditionalInfo original = eventStatisticsManager.getEventWithAdditionalInfo(slug,user.getName()); @@ -168,18 +261,22 @@ private Optional<Event> updateEvent(String slug, EventCreationRequest request, P Event event = original.getEvent(); - EventModification em = request.toEventModificationUpdate(original,organization,imageRef); + EventModification em = request.toEventModificationUpdate(original, organization, imageRef); eventManager.updateEventHeader(event, em, user.getName()); eventManager.updateEventPrices(event, em, user.getName()); if (em.getTicketCategories() != null && !em.getTicketCategories().isEmpty()) { - em.getTicketCategories().forEach(c -> - findCategoryByName(event, c.getName()).ifPresent(originalCategory -> - eventManager.updateCategory(originalCategory.getId(), event.getId(), c, user.getName()) - ) - ); + var existingCategories = original.getTicketCategories(); + em.getTicketCategories().forEach(c -> { + var existingCategory = findExistingCategory(existingCategories, c.getName(), c.getId()); + if (existingCategory.isPresent()) { + eventManager.updateCategory(existingCategory.get().getId(), event.getId(), c, user.getName()); + } else { + eventManager.insertCategory(event.getId(), c, user.getName()); + } + }); } @@ -188,26 +285,21 @@ private Optional<Event> updateEvent(String slug, EventCreationRequest request, P return eventManager.getOptionalByName(slug,user.getName()); } - private Optional<TicketCategory> findCategoryByName(Event event, String name) { - List<TicketCategory> categories = eventManager.loadTicketCategories(event); - return categories.stream().filter( oc -> oc.getName().equals(name)).findFirst(); - } - - private Optional<Event> insertEvent(EventCreationRequest request, Principal user, String imageRef) { - Organization organization = userManager.findUserOrganizations(user.getName()).get(0); - EventModification em = request.toEventModification(organization,eventNameManager::generateShortName,imageRef); - eventManager.createEvent(em, user.getName()); - Optional<Event> event = eventManager.getOptionalByName(em.getShortName(),user.getName()); - - event.ifPresent(e -> { + private Optional<String> insertEvent(EventCreationRequest request, Principal user, String imageRef) { + try { + Organization organization = userManager.findUserOrganizations(user.getName()).get(0); + EventModification em = request.toEventModification(organization,eventNameManager::generateShortName,imageRef); + eventManager.createEvent(em, user.getName()); + var eventWithStatistics = eventStatisticsManager.getEventWithAdditionalInfo(em.getShortName(),user.getName()); + var event = eventWithStatistics.getEvent(); Optional.ofNullable(request.getTickets().getPromoCodes()).ifPresent(promoCodes -> promoCodes.forEach(pc -> //TODO add ref to categories eventManager.addPromoCode( pc.getName(), - e.getId(), + event.getId(), organization.getId(), - ZonedDateTime.of(pc.getValidFrom(),e.getZoneId()), - ZonedDateTime.of(pc.getValidTo(),e.getZoneId()), + ZonedDateTime.of(pc.getValidFrom(),event.getZoneId()), + ZonedDateTime.of(pc.getValidTo(),event.getZoneId()), pc.getDiscount(), pc.getDiscountType(), Collections.emptyList(), @@ -215,7 +307,8 @@ private Optional<Event> insertEvent(EventCreationRequest request, Principal user null, null, PromoCodeDiscount.CodeType.DISCOUNT, - null + null, + pc.getDiscountType() != PromoCodeDiscount.DiscountType.PERCENTAGE ? event.getCurrency() : null ) ) ); @@ -227,16 +320,16 @@ private Optional<Event> insertEvent(EventCreationRequest request, Principal user if(link.getRight().isPresent()) { Group group = link.getRight().get(); EventCreationRequest.CategoryRequest categoryRequest = link.getLeft(); - findCategoryByName(e, categoryRequest.getName()).ifPresent(category -> { + findExistingCategory(eventWithStatistics.getTicketCategories(), categoryRequest.getName(), categoryRequest.getId()).ifPresent(category -> { EventCreationRequest.GroupLinkRequest groupLinkRequest = categoryRequest.getGroupLink(); LinkedGroupModification modification = new LinkedGroupModification(null, group.getId(), - e.getId(), + event.getId(), category.getId(), groupLinkRequest.getType(), groupLinkRequest.getMatchType(), groupLinkRequest.getMaxAllocation()); - groupManager.createLink(group.getId(), e.getId(), modification); + groupManager.createLink(group.getId(), event.getId(), modification); }); } }); @@ -244,7 +337,7 @@ private Optional<Event> insertEvent(EventCreationRequest request, Principal user request.getExtensionSettings().stream() .collect(Collectors.groupingBy(EventCreationRequest.ExtensionSetting::getExtensionId)) .forEach((id,settings) -> { - List<ExtensionSupport.ExtensionMetadataIdAndName> metadata = extensionService.getSingle(organization, e, id) + List<ExtensionSupport.ExtensionMetadataIdAndName> metadata = extensionService.getSingle(organization, event, id) .map(es -> extensionRepository.findAllParametersForExtension(es.getId())) .orElseGet(Collections::emptyList); @@ -257,14 +350,16 @@ private Optional<Event> insertEvent(EventCreationRequest request, Principal user return pair.getRight().isPresent(); }) .map(pair -> new ExtensionMetadataValue(pair.getRight().get().getId(), pair.getLeft().getValue())) - .collect(Collectors.toList()); - extensionService.bulkUpdateEventSettings(organization, e, values); + .toList(); + extensionService.bulkUpdateEventSettings(organization, event, values); }); } - - }); - return event; + return Optional.of(event.getShortName()); + } catch (Exception ex) { + log.error("Error while inserting event", ex); + return Optional.empty(); + } } private String fetchImage(String url) { @@ -275,10 +370,4 @@ private String fetchImage(String url) { return null; } } - - - - - - } diff --git a/src/main/java/alfio/controller/api/v1/admin/OrganizationsApiV1Controller.java b/src/main/java/alfio/controller/api/v1/admin/OrganizationsApiV1Controller.java new file mode 100644 index 0000000000..2dc6af089f --- /dev/null +++ b/src/main/java/alfio/controller/api/v1/admin/OrganizationsApiV1Controller.java @@ -0,0 +1,108 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v1.admin; + +import alfio.manager.OrganizationDeleter; +import alfio.manager.user.UserManager; +import alfio.model.modification.OrganizationModification; +import alfio.model.user.Organization; +import alfio.model.user.Role; +import alfio.model.user.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.List; + +@RestController +@RequestMapping("/api/v1/admin/system/organization") +public class OrganizationsApiV1Controller { + private final UserManager userManager; + private final OrganizationDeleter organizationDeleter; + + @Autowired + public OrganizationsApiV1Controller(UserManager userManager, + OrganizationDeleter organizationDeleter) { + this.userManager = userManager; + this.organizationDeleter = organizationDeleter; + } + + @PostMapping("/create") + public ResponseEntity<Organization> createOrganization(@RequestBody OrganizationModification om, Principal principal) { + if (om == null || !om.isValid(true)) { + return ResponseEntity.badRequest().build(); + } + int orgId = userManager.createOrganization(om, principal); + return ResponseEntity.ok(userManager.findOrganizationById(orgId, UserManager.ADMIN_USERNAME)); + } + + @GetMapping("/list") + public List<Organization> getAllOrganizations() { + return userManager.findUserOrganizations(UserManager.ADMIN_USERNAME); + } + + @GetMapping("/{id}") + public ResponseEntity<Organization> getSingleOrganization(@PathVariable("id") int organizationId) { + return ResponseEntity.of(userManager.findOptionalOrganizationById(organizationId, UserManager.ADMIN_USERNAME)); + } + + @PutMapping("/{id}/api-key") + public OrganizationApiKey createApiKeyForOrganization(@PathVariable("id") int organizationId, Principal principal) { + var user = userManager.insertUser(organizationId, null, null, null, null, Role.fromRoleName("ROLE_API_CLIENT"), User.Type.API_KEY, null, "Auto-generated API Key", principal); + return new OrganizationApiKey(organizationId, user.getUsername()); + } + + @PostMapping("/{id}") + public ResponseEntity<Organization> update(@PathVariable("id") int organizationId, + @RequestBody OrganizationModification om, + Principal principal) { + if (om == null || !om.isValid(false) || organizationId != om.getId()) { + return ResponseEntity.badRequest().build(); + } + userManager.updateOrganization(om, principal); + return ResponseEntity.ok(userManager.findOrganizationById(organizationId, UserManager.ADMIN_USERNAME)); + } + + @DeleteMapping("/{id}") + public ResponseEntity<Void> delete(@PathVariable("id") int organizationId, Principal principal) { + boolean result = organizationDeleter.deleteOrganization(organizationId, principal); + if (result) { + return ResponseEntity.ok().build(); + } else { + return ResponseEntity.badRequest().build(); + } + } + + static class OrganizationApiKey { + private final int organizationId; + private final String apiKey; + + OrganizationApiKey(int organizationId, String apiKey) { + this.organizationId = organizationId; + this.apiKey = apiKey; + } + + public int getOrganizationId() { + return organizationId; + } + + public String getApiKey() { + return apiKey; + } + } +} diff --git a/src/main/java/alfio/controller/api/v1/admin/ReservationApiV1Controller.java b/src/main/java/alfio/controller/api/v1/admin/ReservationApiV1Controller.java index 2f3f7e8a27..1f7ffe17a2 100644 --- a/src/main/java/alfio/controller/api/v1/admin/ReservationApiV1Controller.java +++ b/src/main/java/alfio/controller/api/v1/admin/ReservationApiV1Controller.java @@ -18,14 +18,23 @@ import alfio.manager.EventManager; import alfio.manager.PromoCodeRequestManager; +import alfio.manager.PurchaseContextManager; import alfio.manager.TicketReservationManager; -import alfio.model.api.v1.admin.ReservationCreationRequest; +import alfio.model.Event; +import alfio.model.PurchaseContext; +import alfio.model.PurchaseContext.PurchaseContextType; +import alfio.model.ReservationMetadata; +import alfio.model.api.v1.admin.ReservationAPICreationRequest; +import alfio.model.api.v1.admin.SubscriptionReservationCreationRequest; +import alfio.model.api.v1.admin.TicketReservationCreationRequest; import alfio.model.result.ErrorCode; +import alfio.model.subscription.SubscriptionDescriptor; import alfio.util.ReservationUtil; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.web.bind.annotation.*; @@ -38,46 +47,44 @@ import static java.util.Objects.requireNonNullElseGet; @RestController -@RequestMapping("/api/v1/admin/event") +@RequestMapping("/api/v1/admin") public class ReservationApiV1Controller { private final TicketReservationManager ticketReservationManager; + private final PurchaseContextManager purchaseContextManager; private final EventManager eventManager; private final PromoCodeRequestManager promoCodeRequestManager; @Autowired public ReservationApiV1Controller(TicketReservationManager ticketReservationManager, - EventManager eventManager, - PromoCodeRequestManager promoCodeRequestManager) { + PurchaseContextManager purchaseContextManager, + PromoCodeRequestManager promoCodeRequestManager, + EventManager eventManager) { this.ticketReservationManager = ticketReservationManager; - this.eventManager = eventManager; + this.purchaseContextManager = purchaseContextManager; this.promoCodeRequestManager = promoCodeRequestManager; + this.eventManager = eventManager; } - @PostMapping("/{slug}/reservation") - public ResponseEntity<CreationResponse> createReservation(@PathVariable("slug") String eventSlug, - @RequestBody ReservationCreationRequest reservationCreationRequest, - Principal principal) { + @PostMapping("/event/{slug}/reservation") + @Transactional + public ResponseEntity<CreationResponse> createTicketsReservation(@PathVariable("slug") String eventSlug, + @RequestBody TicketReservationCreationRequest reservationCreationRequest, + Principal principal) { var bindingResult = new BeanPropertyBindingResult(reservationCreationRequest, "reservation"); - var optionalEvent = eventManager.getOptionalByName(eventSlug, principal.getName()); + var optionalEvent = purchaseContextManager.findBy(PurchaseContextType.event, eventSlug); if (optionalEvent.isEmpty()) { return ResponseEntity.notFound().build(); } - var event = optionalEvent.get(); + var event = (Event) optionalEvent.get(); Optional<String> promoCodeDiscount = ReservationUtil.checkPromoCode(reservationCreationRequest, event, promoCodeRequestManager, bindingResult); var locale = Locale.forLanguageTag(requireNonNullElseGet(reservationCreationRequest.getLanguage(), () -> event.getContentLanguages().get(0).getLanguage())); var selected = ReservationUtil.validateCreateRequest(reservationCreationRequest, bindingResult, ticketReservationManager, eventManager, "", event); if(selected.isPresent() && !bindingResult.hasErrors()) { var pair = selected.get(); return ticketReservationManager.createTicketReservation(event, pair.getLeft(), pair.getRight(), promoCodeDiscount, locale, bindingResult, principal) - .map(id -> { - var user = reservationCreationRequest.getUser(); - if(user != null) { - ticketReservationManager.setReservationOwner(id, user.getUsername(), user.getEmail(), user.getFirstName(), user.getLastName(), locale.getLanguage()); - } - return ResponseEntity.ok(CreationResponse.success(id, ticketReservationManager.reservationUrlForExternalClients(id, event, locale.getLanguage(), user != null))); - }) + .map(id -> ResponseEntity.ok(postCreate(reservationCreationRequest, id, event, locale))) .orElseGet(() -> ResponseEntity.badRequest().build()); } else { return ResponseEntity.badRequest() @@ -85,6 +92,54 @@ public ResponseEntity<CreationResponse> createReservation(@PathVariable("slug") } } + @PostMapping("/subscription/{id}/reservation") + @Transactional + public ResponseEntity<CreationResponse> createSubscriptionReservation(@PathVariable("id") String subscriptionId, + @RequestBody SubscriptionReservationCreationRequest creationRequest, + Principal principal) { + var bindingResult = new BeanPropertyBindingResult(creationRequest, "reservation"); + + var optionalDescriptor = purchaseContextManager.findBy(PurchaseContextType.subscription, subscriptionId); + + if (optionalDescriptor.isEmpty()) { + return ResponseEntity.notFound().build(); + } + var subscriptionDescriptor = (SubscriptionDescriptor) optionalDescriptor.get(); + var locale = Locale.forLanguageTag(requireNonNullElseGet(creationRequest.getLanguage(), () -> subscriptionDescriptor.getContentLanguages().get(0).getLanguage())); + return ticketReservationManager.createSubscriptionReservation(subscriptionDescriptor, locale, bindingResult, principal, creationRequest.getMetadataOrNull()) + .map(id -> ResponseEntity.ok(postCreate(creationRequest, id, subscriptionDescriptor, locale))) + .orElseGet(() -> { + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest() + .body( + CreationResponse.error(bindingResult.getAllErrors().stream().map(err -> ErrorCode.custom("invalid."+err.getObjectName(), err.getCode())).collect(Collectors.toList())) + ); + } + return ResponseEntity.badRequest().build(); + }); + } + + private CreationResponse postCreate(ReservationAPICreationRequest creationRequest, + String id, + PurchaseContext purchaseContext, + Locale locale) { + var user = creationRequest.getUser(); + if(user != null) { + ticketReservationManager.setReservationOwner(id, user.getUsername(), user.getEmail(), user.getFirstName(), user.getLastName(), locale.getLanguage()); + } + var reservationConfiguration = creationRequest.getReservationConfiguration(); + if(reservationConfiguration != null) { + var reservationMetadata = new ReservationMetadata(reservationConfiguration.isHideContactData(), + false, + false, + reservationConfiguration.isHideConfirmationButtons(), + reservationConfiguration.isLockEmailEdit()); + ticketReservationManager.setReservationMetadata(id, reservationMetadata); + } + var subscriptionId = creationRequest instanceof TicketReservationCreationRequest ? ((TicketReservationCreationRequest) creationRequest).getSubscriptionId() : null; + return CreationResponse.success(id, ticketReservationManager.reservationUrlForExternalClients(id, purchaseContext, locale.getLanguage(), user != null, subscriptionId)); + } + public static class CreationResponse { private final String id; private final String href; diff --git a/src/main/java/alfio/controller/api/v1/admin/SubscriptionApiV1Controller.java b/src/main/java/alfio/controller/api/v1/admin/SubscriptionApiV1Controller.java new file mode 100644 index 0000000000..52c116a383 --- /dev/null +++ b/src/main/java/alfio/controller/api/v1/admin/SubscriptionApiV1Controller.java @@ -0,0 +1,158 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v1.admin; + +import alfio.controller.api.admin.SubscriptionApiController; +import alfio.manager.EventManager; +import alfio.manager.FileDownloadManager; +import alfio.manager.FileUploadManager; +import alfio.manager.SubscriptionManager; +import alfio.manager.user.UserManager; +import alfio.model.api.v1.admin.SubscriptionDescriptorModificationRequest; +import alfio.model.modification.SubscriptionDescriptorModification; +import alfio.model.subscription.SubscriptionDescriptorWithStatistics; +import alfio.util.Json; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.List; +import java.util.UUID; + +import static alfio.controller.api.admin.SubscriptionApiController.loadLinkedEvents; + +@RestController +@RequestMapping("/api/v1/admin/subscription") +@Transactional +public class SubscriptionApiV1Controller { + private static final Logger log = LoggerFactory.getLogger(SubscriptionApiV1Controller.class); + private final SubscriptionManager subscriptionManager; + private final FileUploadManager fileUploadManager; + private final FileDownloadManager fileDownloadManager; + private final UserManager userManager; + private final EventManager eventManager; + + public SubscriptionApiV1Controller(SubscriptionManager subscriptionManager, + FileUploadManager fileUploadManager, + FileDownloadManager fileDownloadManager, + UserManager userManager, + EventManager eventManager) { + this.subscriptionManager = subscriptionManager; + this.fileUploadManager = fileUploadManager; + this.fileDownloadManager = fileDownloadManager; + this.userManager = userManager; + this.eventManager = eventManager; + } + + @PostMapping("/create") + public ResponseEntity<String> create(@RequestBody SubscriptionDescriptorModificationRequest request, Principal principal) { + var organization = userManager.findUserOrganizations(principal.getName()).get(0); + String imageRef = null; + if(StringUtils.isNotEmpty(request.getImageUrl())) { + imageRef = fetchImage(request.getImageUrl()); + } + var modification = request.toDescriptorModification(null, organization.getId(), imageRef) + .flatMap(SubscriptionDescriptorModification::validate); + if (modification.isSuccess()) { + // request is valid + var optionalId = subscriptionManager.createSubscriptionDescriptor(modification.getData()); + return optionalId.map(uuid -> ResponseEntity.ok(uuid.toString())) + .orElseGet(() -> ResponseEntity.internalServerError().build()); + } + return ResponseEntity.badRequest().body(Json.toJson(modification.getErrors())); + + } + + @PostMapping("/{subscriptionId}/update") + public ResponseEntity<String> update(@PathVariable("subscriptionId") UUID subscriptionId, + @RequestBody SubscriptionDescriptorModificationRequest request, Principal principal) { + var organization = userManager.findUserOrganizations(principal.getName()).get(0); + String imageRef = null; + if(StringUtils.isNotEmpty(request.getImageUrl())) { + imageRef = fetchImage(request.getImageUrl()); + } + var modification = request.toDescriptorModification(subscriptionId, organization.getId(), imageRef) + .flatMap(SubscriptionDescriptorModification::validate); + if (modification.isSuccess()) { + // request is valid + var optionalId = subscriptionManager.updateSubscriptionDescriptor(modification.getData()); + return optionalId.map(uuid -> ResponseEntity.ok(uuid.toString())) + .orElseGet(() -> ResponseEntity.internalServerError().build()); + } + return ResponseEntity.badRequest().body(Json.toJson(modification.getErrors())); + } + + @GetMapping("/{subscriptionId}") + public ResponseEntity<SubscriptionDescriptorWithStatistics> get(@PathVariable("subscriptionId") UUID subscriptionId, + Principal principal) { + var organization = userManager.findUserOrganizations(principal.getName()).get(0); + return ResponseEntity.of(subscriptionManager.loadSubscriptionWithStatistics(subscriptionId, organization.getId())); + } + + @GetMapping("/{subscriptionId}/events") + public ResponseEntity<List<String>> getLinkedEvents(@PathVariable("subscriptionId") UUID subscriptionId, + Principal principal) { + var organization = userManager.findUserOrganizations(principal.getName()).get(0); + return ResponseEntity.ok(loadLinkedEvents(subscriptionManager.getLinkedEvents(organization.getId(), subscriptionId))); + } + + @PostMapping("/{subscriptionId}/events") + public ResponseEntity<List<String>> updateLinkedEvents(@PathVariable("subscriptionId") UUID subscriptionId, + @RequestBody List<String> eventSlugs, + Principal principal) { + if (eventSlugs == null) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + var organization = userManager.findUserOrganizations(principal.getName()).get(0); + int organizationId = organization.getId(); + + List<Integer> eventIds = List.of(); + if (!eventSlugs.isEmpty()) { + eventIds = eventManager.getEventIdsBySlug(eventSlugs, organizationId); + } + var result = subscriptionManager.updateLinkedEvents(organizationId, subscriptionId, eventIds); + if (result.isSuccess()) { + return ResponseEntity.ok(loadLinkedEvents(result.getData())); + } else { + if (log.isWarnEnabled()) { + log.warn("Cannot update linked events {}", Json.toJson(result.getErrors())); + } + return ResponseEntity.badRequest().build(); + } + } + + @DeleteMapping("/{subscriptionId}/deactivate") + public ResponseEntity<Void> deactivate(@PathVariable("subscriptionId") UUID descriptorId, Principal principal) { + var organization = userManager.findUserOrganizations(principal.getName()).get(0); + int organizationId = organization.getId(); + return SubscriptionApiController.deactivateSubscriptionDescriptor(organizationId, descriptorId, subscriptionManager); + } + + private String fetchImage(String url) { + if(url != null) { + FileDownloadManager.DownloadedFile file = fileDownloadManager.downloadFile(url); + return file != null ? fileUploadManager.insertFile(file.toUploadBase64FileModification()) : null; + } else { + return null; + } + } +} diff --git a/src/main/java/alfio/controller/api/v2/InfoApiController.java b/src/main/java/alfio/controller/api/v2/InfoApiController.java index ada7168c22..9bc2611af7 100644 --- a/src/main/java/alfio/controller/api/v2/InfoApiController.java +++ b/src/main/java/alfio/controller/api/v2/InfoApiController.java @@ -18,7 +18,6 @@ import alfio.controller.api.v2.model.AlfioInfo; import alfio.manager.system.ConfigurationManager; -import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -27,11 +26,14 @@ @RestController @RequestMapping("/api/v2/") -@AllArgsConstructor public class InfoApiController { private final ConfigurationManager configurationManager; + public InfoApiController(ConfigurationManager configurationManager) { + this.configurationManager = configurationManager; + } + @GetMapping("info") public AlfioInfo getInfo(HttpSession session) { return configurationManager.getInfo(session); diff --git a/src/main/java/alfio/controller/api/v2/TranslationsApiController.java b/src/main/java/alfio/controller/api/v2/TranslationsApiController.java index d493753c05..736322870e 100644 --- a/src/main/java/alfio/controller/api/v2/TranslationsApiController.java +++ b/src/main/java/alfio/controller/api/v2/TranslationsApiController.java @@ -24,7 +24,6 @@ import alfio.manager.system.ConfigurationManager; import alfio.model.system.ConfigurationKeys; import alfio.util.LocaleUtil; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.tuple.Pair; import org.springframework.web.bind.annotation.*; @@ -35,7 +34,6 @@ import java.util.stream.Collectors; @RestController -@AllArgsConstructor @RequestMapping("/api/v2/") public class TranslationsApiController { @@ -43,16 +41,22 @@ public class TranslationsApiController { private final ConfigurationManager configurationManager; private final I18nManager i18nManager; + public TranslationsApiController(MessageSourceManager messageSourceManager, ConfigurationManager configurationManager, I18nManager i18nManager) { + this.messageSourceManager = messageSourceManager; + this.configurationManager = configurationManager; + this.i18nManager = i18nManager; + } + @GetMapping("/public/i18n/bundle/{lang}") public Map<String, String> getPublicTranslations(@PathVariable("lang") String lang, @RequestParam(value = "withSystemOverride", defaultValue = "true", required = false) boolean withSystemOverride) { - return messageSourceManager.getBundleAsMap("alfio.i18n.public", withSystemOverride, lang); + return messageSourceManager.getBundleAsMap("alfio.i18n.public", withSystemOverride, lang, MessageSourceManager.PUBLIC_FRONTEND); } @GetMapping("/admin/i18n/bundle/{lang}") public Map<String, String> getAdminTranslations(@PathVariable("lang") String lang, @RequestParam(value = "withSystemOverride", defaultValue = "true", required = false) boolean withSystemOverride) { - return messageSourceManager.getBundleAsMap("alfio.i18n.admin", withSystemOverride, lang); + return messageSourceManager.getBundleAsMap("alfio.i18n.public", withSystemOverride, lang, MessageSourceManager.ADMIN_FRONTEND); } @GetMapping("/public/i18n/countries/{lang}") diff --git a/src/main/java/alfio/controller/api/v2/model/AdditionalService.java b/src/main/java/alfio/controller/api/v2/model/AdditionalService.java index 208cccfe18..d9c2aef3f6 100644 --- a/src/main/java/alfio/controller/api/v2/model/AdditionalService.java +++ b/src/main/java/alfio/controller/api/v2/model/AdditionalService.java @@ -16,12 +16,10 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Map; -@AllArgsConstructor @Getter public class AdditionalService { @@ -52,4 +50,44 @@ public class AdditionalService { private final Map<String, String> title; private final Map<String, String> description; + + public AdditionalService(int id, + alfio.model.AdditionalService.AdditionalServiceType type, + alfio.model.AdditionalService.SupplementPolicy supplementPolicy, + boolean fixPrice, + int availableQuantity, + int maxQtyPerOrder, + boolean free, + String formattedFinalPrice, + boolean hasDiscount, + String formattedDiscountedPrice, + boolean vatApplies, + boolean vatIncluded, + String vatPercentage, + boolean expired, + boolean saleInFuture, + Map<String, String> formattedInception, + Map<String, String> formattedExpiration, + Map<String, String> title, + Map<String, String> description) { + this.id = id; + this.type = type; + this.supplementPolicy = supplementPolicy; + this.fixPrice = fixPrice; + this.availableQuantity = availableQuantity; + this.maxQtyPerOrder = maxQtyPerOrder; + this.free = free; + this.formattedFinalPrice = formattedFinalPrice; + this.hasDiscount = hasDiscount; + this.formattedDiscountedPrice = formattedDiscountedPrice; + this.vatApplies = vatApplies; + this.vatIncluded = vatIncluded; + this.vatPercentage = vatPercentage; + this.expired = expired; + this.saleInFuture = saleInFuture; + this.formattedInception = formattedInception; + this.formattedExpiration = formattedExpiration; + this.title = title; + this.description = description; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/AlfioInfo.java b/src/main/java/alfio/controller/api/v2/model/AlfioInfo.java index 6524e7c273..26f5ceef8d 100644 --- a/src/main/java/alfio/controller/api/v2/model/AlfioInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/AlfioInfo.java @@ -17,11 +17,9 @@ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; @Getter -@AllArgsConstructor public class AlfioInfo { private final boolean demoModeEnabled; @@ -31,4 +29,26 @@ public class AlfioInfo { private final String globalPrivacyPolicyUrl; private final String globalTermsUrl; private final InvoicingConfiguration invoicingConfiguration; + private final String announcementBannerContentHTML; + private final WalletConfiguration walletConfiguration; + + public AlfioInfo(boolean demoModeEnabled, + boolean devModeEnabled, + boolean prodModeEnabled, + AnalyticsConfiguration analyticsConfiguration, + String globalPrivacyPolicyUrl, + String globalTermsUrl, + InvoicingConfiguration invoicingConfiguration, + String announcementBannerContentHTML, + WalletConfiguration walletConfiguration) { + this.demoModeEnabled = demoModeEnabled; + this.devModeEnabled = devModeEnabled; + this.prodModeEnabled = prodModeEnabled; + this.analyticsConfiguration = analyticsConfiguration; + this.globalPrivacyPolicyUrl = globalPrivacyPolicyUrl; + this.globalTermsUrl = globalTermsUrl; + this.invoicingConfiguration = invoicingConfiguration; + this.announcementBannerContentHTML = announcementBannerContentHTML; + this.walletConfiguration = walletConfiguration; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/AnalyticsConfiguration.java b/src/main/java/alfio/controller/api/v2/model/AnalyticsConfiguration.java index 94068d1ac3..cc96437282 100644 --- a/src/main/java/alfio/controller/api/v2/model/AnalyticsConfiguration.java +++ b/src/main/java/alfio/controller/api/v2/model/AnalyticsConfiguration.java @@ -18,7 +18,6 @@ import alfio.manager.system.ConfigurationManager; import alfio.model.system.ConfigurationKeys; -import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; @@ -29,13 +28,18 @@ import static alfio.model.system.ConfigurationKeys.GOOGLE_ANALYTICS_ANONYMOUS_MODE; import static alfio.model.system.ConfigurationKeys.GOOGLE_ANALYTICS_KEY; -@AllArgsConstructor @Getter public class AnalyticsConfiguration { private final String googleAnalyticsKey; private final boolean googleAnalyticsScrambledInfo; //<- see GOOGLE_ANALYTICS_ANONYMOUS_MODE private final String clientId; + public AnalyticsConfiguration(String googleAnalyticsKey, boolean googleAnalyticsScrambledInfo, String clientId) { + this.googleAnalyticsKey = googleAnalyticsKey; + this.googleAnalyticsScrambledInfo = googleAnalyticsScrambledInfo; + this.clientId = clientId; + } + public static AnalyticsConfiguration build(Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> conf, HttpSession session) { var googAnalyticsKey = StringUtils.trimToNull(conf.get(GOOGLE_ANALYTICS_KEY).getValueOrNull()); diff --git a/src/main/java/alfio/controller/api/v2/model/ApiPurchaseContext.java b/src/main/java/alfio/controller/api/v2/model/ApiPurchaseContext.java index 2209ca0245..40075c8d65 100644 --- a/src/main/java/alfio/controller/api/v2/model/ApiPurchaseContext.java +++ b/src/main/java/alfio/controller/api/v2/model/ApiPurchaseContext.java @@ -30,7 +30,12 @@ public interface ApiPurchaseContext { InvoicingConfiguration getInvoicingConfiguration(); EventWithAdditionalInfo.AssignmentConfiguration getAssignmentConfiguration(); AnalyticsConfiguration getAnalyticsConfiguration(); + + OfflinePaymentConfiguration getOfflinePaymentConfiguration(); EventWithAdditionalInfo.CaptchaConfiguration getCaptchaConfiguration(); + + EmbeddingConfiguration getEmbeddingConfiguration(); + boolean isVatIncluded(); boolean isFree(); String getCurrency(); diff --git a/src/main/java/alfio/controller/api/v2/model/BasicEventInfo.java b/src/main/java/alfio/controller/api/v2/model/BasicEventInfo.java index e5801f9493..0b24aaca41 100644 --- a/src/main/java/alfio/controller/api/v2/model/BasicEventInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/BasicEventInfo.java @@ -17,13 +17,11 @@ package alfio.controller.api.v2.model; import alfio.model.Event.EventFormat; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; import java.util.Map; -@AllArgsConstructor @Getter public class BasicEventInfo implements DateValidity { @@ -41,4 +39,32 @@ public class BasicEventInfo implements DateValidity { private final Map<String, String> formattedEndDate; private final Map<String, String> formattedEndTime; private final List<Language> contentLanguages; + + public BasicEventInfo(String shortName, + String fileBlobId, + Map<String, String> title, + EventFormat format, + String location, + String timeZone, + DatesWithTimeZoneOffset datesWithOffset, + boolean sameDay, + Map<String, String> formattedBeginDate, + Map<String, String> formattedBeginTime, + Map<String, String> formattedEndDate, + Map<String, String> formattedEndTime, + List<Language> contentLanguages) { + this.shortName = shortName; + this.fileBlobId = fileBlobId; + this.title = title; + this.format = format; + this.location = location; + this.timeZone = timeZone; + this.datesWithOffset = datesWithOffset; + this.sameDay = sameDay; + this.formattedBeginDate = formattedBeginDate; + this.formattedBeginTime = formattedBeginTime; + this.formattedEndDate = formattedEndDate; + this.formattedEndTime = formattedEndTime; + this.contentLanguages = contentLanguages; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/BasicSubscriptionDescriptorInfo.java b/src/main/java/alfio/controller/api/v2/model/BasicSubscriptionDescriptorInfo.java index 9d48046648..3371f87a0b 100644 --- a/src/main/java/alfio/controller/api/v2/model/BasicSubscriptionDescriptorInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/BasicSubscriptionDescriptorInfo.java @@ -20,7 +20,6 @@ import alfio.model.subscription.SubscriptionDescriptor.SubscriptionTimeUnit; import alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType; import alfio.model.subscription.SubscriptionDescriptor.SubscriptionValidityType; -import lombok.AllArgsConstructor; import lombok.Getter; import java.math.BigDecimal; @@ -30,7 +29,6 @@ import java.util.UUID; @Getter -@AllArgsConstructor public class BasicSubscriptionDescriptorInfo { private final UUID id; private final String fileBlobId; @@ -62,4 +60,52 @@ public class BasicSubscriptionDescriptorInfo { private final Map<String, String> formattedValidTo; private final List<Language> contentLanguages; + + public BasicSubscriptionDescriptorInfo(UUID id, + String fileBlobId, + Map<String, String> title, + Map<String, String> description, + DatesWithTimeZoneOffset salePeriod, + SubscriptionValidityType validityType, + SubscriptionUsageType usageType, + ZoneId timeZone, + SubscriptionTimeUnit validityTimeUnit, + Integer validityUnits, + Integer maxEntries, + String organizationEmail, + String organizationName, + String formattedPrice, + String currency, + CurrencyDescriptor currencyDescriptor, + BigDecimal vat, + boolean vatIncluded, + Map<String, String> formattedOnSaleFrom, + Map<String, String> formattedOnSaleTo, + Map<String, String> formattedValidFrom, + Map<String, String> formattedValidTo, + List<Language> contentLanguages) { + this.id = id; + this.fileBlobId = fileBlobId; + this.title = title; + this.description = description; + this.salePeriod = salePeriod; + this.validityType = validityType; + this.usageType = usageType; + this.timeZone = timeZone; + this.validityTimeUnit = validityTimeUnit; + this.validityUnits = validityUnits; + this.maxEntries = maxEntries; + this.organizationEmail = organizationEmail; + this.organizationName = organizationName; + this.formattedPrice = formattedPrice; + this.currency = currency; + this.currencyDescriptor = currencyDescriptor; + this.vat = vat; + this.vatIncluded = vatIncluded; + this.formattedOnSaleFrom = formattedOnSaleFrom; + this.formattedOnSaleTo = formattedOnSaleTo; + this.formattedValidFrom = formattedValidFrom; + this.formattedValidTo = formattedValidTo; + this.contentLanguages = contentLanguages; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/ClientRedirect.java b/src/main/java/alfio/controller/api/v2/model/ClientRedirect.java index 79765c36ac..5703ea5ba4 100644 --- a/src/main/java/alfio/controller/api/v2/model/ClientRedirect.java +++ b/src/main/java/alfio/controller/api/v2/model/ClientRedirect.java @@ -16,15 +16,17 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.commons.lang3.StringUtils; @Getter -@AllArgsConstructor public class ClientRedirect { private final String targetUrl; + public ClientRedirect(String targetUrl) { + this.targetUrl = targetUrl; + } + public boolean isEmpty() { return StringUtils.isBlank(targetUrl); } diff --git a/src/main/java/alfio/controller/api/v2/model/DynamicDiscount.java b/src/main/java/alfio/controller/api/v2/model/DynamicDiscount.java index 31a567daf1..6cf029fb9e 100644 --- a/src/main/java/alfio/controller/api/v2/model/DynamicDiscount.java +++ b/src/main/java/alfio/controller/api/v2/model/DynamicDiscount.java @@ -17,13 +17,10 @@ package alfio.controller.api.v2.model; import alfio.model.PromoCodeDiscount; -import lombok.Data; import java.util.Map; -@Data -public class DynamicDiscount { - private final String discount; - private final PromoCodeDiscount.DiscountType discountType; - private final Map<String, String> formattedMessage; +public record DynamicDiscount(String discount, + PromoCodeDiscount.DiscountType discountType, + Map<String, String> formattedMessage) { } diff --git a/src/main/java/alfio/config/authentication/AuthenticationConstants.java b/src/main/java/alfio/controller/api/v2/model/EmbeddingConfiguration.java similarity index 53% rename from src/main/java/alfio/config/authentication/AuthenticationConstants.java rename to src/main/java/alfio/controller/api/v2/model/EmbeddingConfiguration.java index fbb0827a8d..8bbd1ce648 100644 --- a/src/main/java/alfio/config/authentication/AuthenticationConstants.java +++ b/src/main/java/alfio/controller/api/v2/model/EmbeddingConfiguration.java @@ -14,19 +14,22 @@ * You should have received a copy of the GNU General Public License * along with alf.io. If not, see <http://www.gnu.org/licenses/>. */ -package alfio.config.authentication; +package alfio.controller.api.v2.model; -import lombok.experimental.UtilityClass; +import java.util.Objects; -@UtilityClass -public class AuthenticationConstants { - public static final String OPERATOR = "OPERATOR"; - public static final String SPONSOR = "SPONSOR"; - static final String ADMIN_API = "/admin/api"; - static final String ADMIN_PUBLIC_API = "/api/v1/admin"; - static final String SUPERVISOR = "SUPERVISOR"; - static final String ADMIN = "ADMIN"; - static final String OWNER = "OWNER"; - static final String API_CLIENT = "API_CLIENT"; - static final String X_REQUESTED_WITH = "X-Requested-With"; +public class EmbeddingConfiguration { + private final String notificationOrigin; + + public EmbeddingConfiguration(String notificationOrigin) { + this.notificationOrigin = Objects.requireNonNullElse(notificationOrigin, ""); + } + + public boolean isEnabled() { + return notificationOrigin != null && !notificationOrigin.isBlank(); + } + + public String getNotificationOrigin() { + return notificationOrigin; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/EventCode.java b/src/main/java/alfio/controller/api/v2/model/EventCode.java index 681c000c0d..288afd4bb5 100644 --- a/src/main/java/alfio/controller/api/v2/model/EventCode.java +++ b/src/main/java/alfio/controller/api/v2/model/EventCode.java @@ -17,10 +17,8 @@ package alfio.controller.api.v2.model; import alfio.model.PromoCodeDiscount; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class EventCode { @@ -29,6 +27,13 @@ public class EventCode { private final PromoCodeDiscount.DiscountType discountType; private final String discountAmount; + public EventCode(String code, EventCodeType type, PromoCodeDiscount.DiscountType discountType, String discountAmount) { + this.code = code; + this.type = type; + this.discountType = discountType; + this.discountAmount = discountAmount; + } + public enum EventCodeType { @Deprecated SPECIAL_PRICE, DISCOUNT, ACCESS } diff --git a/src/main/java/alfio/controller/api/v2/model/EventWithAdditionalInfo.java b/src/main/java/alfio/controller/api/v2/model/EventWithAdditionalInfo.java index 2a794d4755..eadff8876e 100644 --- a/src/main/java/alfio/controller/api/v2/model/EventWithAdditionalInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/EventWithAdditionalInfo.java @@ -21,13 +21,11 @@ import alfio.model.PurchaseContext; import alfio.model.user.Organization; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; import java.util.Map; -@AllArgsConstructor public class EventWithAdditionalInfo implements DateValidity, ApiPurchaseContext { private final Event event; private final String mapUrl; @@ -64,6 +62,10 @@ public class EventWithAdditionalInfo implements DateValidity, ApiPurchaseContext private final AnalyticsConfiguration analyticsConfiguration; + private final OfflinePaymentConfiguration offlinePaymentConfiguration; + + private final EmbeddingConfiguration embeddingConfiguration; + private final Map<String, Map<String, String>> i18nOverride; private final Integer availableTicketsCount; @@ -72,6 +74,50 @@ public class EventWithAdditionalInfo implements DateValidity, ApiPurchaseContext private final boolean canApplySubscriptions; + public EventWithAdditionalInfo(Event event, + String mapUrl, + Organization.OrganizationContact organization, + Map<String, String> description, + String bankAccount, + List<String> bankAccountOwner, + Map<String, String> formattedBeginDate, + Map<String, String> formattedBeginTime, + Map<String, String> formattedEndDate, + Map<String, String> formattedEndTime, + InvoicingConfiguration invoicingConfiguration, + CaptchaConfiguration captchaConfiguration, + AssignmentConfiguration assignmentConfiguration, + PromotionsConfiguration promotionsConfiguration, + AnalyticsConfiguration analyticsConfiguration, + OfflinePaymentConfiguration offlinePaymentConfiguration, + EmbeddingConfiguration embeddingConfiguration, + Map<String, Map<String, String>> i18nOverride, + Integer availableTicketsCount, + String customCss, + boolean canApplySubscriptions) { + this.event = event; + this.mapUrl = mapUrl; + this.organization = organization; + this.description = description; + this.bankAccount = bankAccount; + this.bankAccountOwner = bankAccountOwner; + this.formattedBeginDate = formattedBeginDate; + this.formattedBeginTime = formattedBeginTime; + this.formattedEndDate = formattedEndDate; + this.formattedEndTime = formattedEndTime; + this.invoicingConfiguration = invoicingConfiguration; + this.captchaConfiguration = captchaConfiguration; + this.assignmentConfiguration = assignmentConfiguration; + this.promotionsConfiguration = promotionsConfiguration; + this.analyticsConfiguration = analyticsConfiguration; + this.offlinePaymentConfiguration = offlinePaymentConfiguration; + this.embeddingConfiguration = embeddingConfiguration; + this.i18nOverride = i18nOverride; + this.availableTicketsCount = availableTicketsCount; + this.customCss = customCss; + this.canApplySubscriptions = canApplySubscriptions; + } + public String getShortName() { return event.getShortName(); } @@ -236,27 +282,52 @@ public Map<String, String> getTitle() { return event.getTitle(); } - @AllArgsConstructor + + @Override + public OfflinePaymentConfiguration getOfflinePaymentConfiguration() { + return offlinePaymentConfiguration; + } + + @Override + public EmbeddingConfiguration getEmbeddingConfiguration() { + return embeddingConfiguration; + } + @Getter public static class CaptchaConfiguration { private final boolean captchaForTicketSelection; private final boolean captchaForOfflinePaymentAndFree; private final String recaptchaApiKey; + + public CaptchaConfiguration(boolean captchaForTicketSelection, boolean captchaForOfflinePaymentAndFree, String recaptchaApiKey) { + this.captchaForTicketSelection = captchaForTicketSelection; + this.captchaForOfflinePaymentAndFree = captchaForOfflinePaymentAndFree; + this.recaptchaApiKey = recaptchaApiKey; + } } - @AllArgsConstructor @Getter public static class AssignmentConfiguration { private final boolean forceAssignment; private final boolean enableAttendeeAutocomplete; private final boolean enableTicketTransfer; + + public AssignmentConfiguration(boolean forceAssignment, boolean enableAttendeeAutocomplete, boolean enableTicketTransfer) { + this.forceAssignment = forceAssignment; + this.enableAttendeeAutocomplete = enableAttendeeAutocomplete; + this.enableTicketTransfer = enableTicketTransfer; + } } - @AllArgsConstructor @Getter public static class PromotionsConfiguration { private final boolean hasAccessPromotions; private final boolean usePartnerCode; + + public PromotionsConfiguration(boolean hasAccessPromotions, boolean usePartnerCode) { + this.hasAccessPromotions = hasAccessPromotions; + this.usePartnerCode = usePartnerCode; + } } @JsonIgnore diff --git a/src/main/java/alfio/controller/api/v2/model/InvoicingConfiguration.java b/src/main/java/alfio/controller/api/v2/model/InvoicingConfiguration.java index 9a34e19e42..bae57fab8a 100644 --- a/src/main/java/alfio/controller/api/v2/model/InvoicingConfiguration.java +++ b/src/main/java/alfio/controller/api/v2/model/InvoicingConfiguration.java @@ -16,10 +16,8 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class InvoicingConfiguration { private final boolean userCanDownloadReceiptOrInvoice; @@ -29,4 +27,20 @@ public class InvoicingConfiguration { private final boolean customerReferenceEnabled; private final boolean enabledItalyEInvoicing; private final boolean vatNumberStrictlyRequired; + + public InvoicingConfiguration(boolean userCanDownloadReceiptOrInvoice, + boolean euVatCheckingEnabled, + boolean invoiceAllowed, + boolean onlyInvoice, + boolean customerReferenceEnabled, + boolean enabledItalyEInvoicing, + boolean vatNumberStrictlyRequired) { + this.userCanDownloadReceiptOrInvoice = userCanDownloadReceiptOrInvoice; + this.euVatCheckingEnabled = euVatCheckingEnabled; + this.invoiceAllowed = invoiceAllowed; + this.onlyInvoice = onlyInvoice; + this.customerReferenceEnabled = customerReferenceEnabled; + this.enabledItalyEInvoicing = enabledItalyEInvoicing; + this.vatNumberStrictlyRequired = vatNumberStrictlyRequired; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/ItemsByCategory.java b/src/main/java/alfio/controller/api/v2/model/ItemsByCategory.java index 678c36bbce..4a4c9c2a41 100644 --- a/src/main/java/alfio/controller/api/v2/model/ItemsByCategory.java +++ b/src/main/java/alfio/controller/api/v2/model/ItemsByCategory.java @@ -16,12 +16,10 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; -@AllArgsConstructor @Getter public class ItemsByCategory { @@ -33,11 +31,28 @@ public class ItemsByCategory { private final boolean preSales; private final List<TicketCategoryForWaitingList> ticketCategoriesForWaitingList; + public ItemsByCategory(List<TicketCategory> ticketCategories, + List<TicketCategory> expiredCategories, + List<AdditionalService> additionalServices, + boolean waitingList, + boolean preSales, + List<TicketCategoryForWaitingList> ticketCategoriesForWaitingList) { + this.ticketCategories = ticketCategories; + this.expiredCategories = expiredCategories; + this.additionalServices = additionalServices; + this.waitingList = waitingList; + this.preSales = preSales; + this.ticketCategoriesForWaitingList = ticketCategoriesForWaitingList; + } - @AllArgsConstructor @Getter public static class TicketCategoryForWaitingList { private final int id; private final String name; + + public TicketCategoryForWaitingList(int id, String name) { + this.id = id; + this.name = name; + } } } diff --git a/src/main/java/alfio/controller/api/v2/model/Language.java b/src/main/java/alfio/controller/api/v2/model/Language.java index 01f9946b8a..c659cfb694 100644 --- a/src/main/java/alfio/controller/api/v2/model/Language.java +++ b/src/main/java/alfio/controller/api/v2/model/Language.java @@ -16,12 +16,15 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class Language { private final String locale; private final String displayLanguage; + + public Language(String locale, String displayLanguage) { + this.locale = locale; + this.displayLanguage = displayLanguage; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/LocalizedCountry.java b/src/main/java/alfio/controller/api/v2/model/LocalizedCountry.java index 67d2c67839..af6a2f0732 100644 --- a/src/main/java/alfio/controller/api/v2/model/LocalizedCountry.java +++ b/src/main/java/alfio/controller/api/v2/model/LocalizedCountry.java @@ -16,12 +16,15 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class LocalizedCountry { private final String isoCode; private final String name; + + public LocalizedCountry(String isoCode, String name) { + this.isoCode = isoCode; + this.name = name; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/OfflinePaymentConfiguration.java b/src/main/java/alfio/controller/api/v2/model/OfflinePaymentConfiguration.java new file mode 100644 index 0000000000..1fb033fe95 --- /dev/null +++ b/src/main/java/alfio/controller/api/v2/model/OfflinePaymentConfiguration.java @@ -0,0 +1,29 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v2.model; + +public class OfflinePaymentConfiguration { + private final boolean showOnlyBasicInstructions; + + public OfflinePaymentConfiguration(boolean showOnlyBasicInstructions) { + this.showOnlyBasicInstructions = showOnlyBasicInstructions; + } + + public boolean isShowOnlyBasicInstructions() { + return showOnlyBasicInstructions; + } +} diff --git a/src/main/java/alfio/controller/api/v2/model/OnlineCheckInInfo.java b/src/main/java/alfio/controller/api/v2/model/OnlineCheckInInfo.java index 02f226f99c..b7f7446e04 100644 --- a/src/main/java/alfio/controller/api/v2/model/OnlineCheckInInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/OnlineCheckInInfo.java @@ -16,22 +16,17 @@ */ package alfio.controller.api.v2.model; -import alfio.controller.support.FormattedEventDates; -import alfio.controller.support.Formatters; import alfio.model.checkin.EventWithCheckInInfo; import alfio.model.metadata.JoinLink; -import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.context.MessageSource; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.List; import java.util.Map; import static alfio.controller.support.Formatters.getFormattedDate; -@AllArgsConstructor @Getter public class OnlineCheckInInfo implements DateValidity { @@ -43,6 +38,20 @@ public class OnlineCheckInInfo implements DateValidity { private final String timeZone; private final DatesWithTimeZoneOffset datesWithOffset; + public OnlineCheckInInfo(Map<String, String> formattedBeginDate, + Map<String, String> formattedBeginTime, + Map<String, String> formattedEndDate, + Map<String, String> formattedEndTime, + String timeZone, + DatesWithTimeZoneOffset datesWithOffset) { + this.formattedBeginDate = formattedBeginDate; + this.formattedBeginTime = formattedBeginTime; + this.formattedEndDate = formattedEndDate; + this.formattedEndTime = formattedEndTime; + this.timeZone = timeZone; + this.datesWithOffset = datesWithOffset; + } + public boolean isSameDay() { return true; } @@ -58,8 +67,8 @@ public static OnlineCheckInInfo fromJoinLink(JoinLink joinLink, EventWithCheckInInfo event, ZoneId targetTz, MessageSource messageSource) { - var start = joinLink.getValidFrom().atZone(event.getZoneId()); - var end = joinLink.getValidFrom().atZone(event.getZoneId()); + var start = joinLink.validFrom().atZone(event.getZoneId()); + var end = joinLink.validFrom().atZone(event.getZoneId()); return fromDates(event, start, end, targetTz, messageSource); } diff --git a/src/main/java/alfio/controller/api/v2/model/PaymentProxyWithParameters.java b/src/main/java/alfio/controller/api/v2/model/PaymentProxyWithParameters.java index 75b4cdcd7d..caa161971e 100644 --- a/src/main/java/alfio/controller/api/v2/model/PaymentProxyWithParameters.java +++ b/src/main/java/alfio/controller/api/v2/model/PaymentProxyWithParameters.java @@ -17,14 +17,17 @@ package alfio.controller.api.v2.model; import alfio.model.transaction.PaymentProxy; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Map; -@AllArgsConstructor @Getter public class PaymentProxyWithParameters { private final PaymentProxy paymentProxy; private final Map<String, ?> parameters; + + public PaymentProxyWithParameters(PaymentProxy paymentProxy, Map<String, ?> parameters) { + this.paymentProxy = paymentProxy; + this.parameters = parameters; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/PurchaseContextWithReservations.java b/src/main/java/alfio/controller/api/v2/model/PurchaseContextWithReservations.java index 9456804fd2..280252f647 100644 --- a/src/main/java/alfio/controller/api/v2/model/PurchaseContextWithReservations.java +++ b/src/main/java/alfio/controller/api/v2/model/PurchaseContextWithReservations.java @@ -19,8 +19,6 @@ import alfio.model.PurchaseContext; import alfio.model.ReservationWithPurchaseContext; import alfio.util.LocaleUtil; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.Validate; import java.time.ZonedDateTime; @@ -30,16 +28,11 @@ import java.util.Map; import java.util.stream.Collectors; -@RequiredArgsConstructor -@Getter -public class PurchaseContextWithReservations { - private final Map<String, String> title; - private final String publicIdentifier; - private final PurchaseContext.PurchaseContextType type; - private final Map<String, String> formattedStartDate; - private final Map<String, String> formattedEndDate; - private final boolean sameDay; - private final List<ReservationHeader> reservations; +public record PurchaseContextWithReservations(Map<String, String> title, String publicIdentifier, + PurchaseContext.PurchaseContextType type, + Map<String, String> formattedStartDate, + Map<String, String> formattedEndDate, boolean sameDay, + List<ReservationHeader> reservations) { public static PurchaseContextWithReservations from(List<ReservationWithPurchaseContext> reservations, Map<Locale, String> datePatternsMap) { diff --git a/src/main/java/alfio/controller/api/v2/model/ReservationHeader.java b/src/main/java/alfio/controller/api/v2/model/ReservationHeader.java index a599fa1882..51fa5864c6 100644 --- a/src/main/java/alfio/controller/api/v2/model/ReservationHeader.java +++ b/src/main/java/alfio/controller/api/v2/model/ReservationHeader.java @@ -20,7 +20,6 @@ import alfio.model.ReservationWithPurchaseContext; import alfio.model.TicketReservation; import lombok.Getter; -import lombok.RequiredArgsConstructor; import java.math.BigDecimal; import java.util.List; @@ -29,7 +28,6 @@ import static alfio.util.LocaleUtil.formatDate; -@RequiredArgsConstructor @Getter public class ReservationHeader { private final String id; @@ -44,6 +42,30 @@ public class ReservationHeader { private final PriceContainer.VatStatus vatStatus; private final List<ReservationWithPurchaseContext.PurchaseContextItem> items; + public ReservationHeader(String id, + TicketReservation.TicketReservationStatus status, + Map<String, String> formattedExpiresOn, + Map<String, String> formattedConfirmedOn, + Map<String, String> formattedCreatedOn, + String invoiceNumber, + BigDecimal finalPrice, + String currencyCode, + BigDecimal usedVatPercent, + PriceContainer.VatStatus vatStatus, + List<ReservationWithPurchaseContext.PurchaseContextItem> items) { + this.id = id; + this.status = status; + this.formattedExpiresOn = formattedExpiresOn; + this.formattedConfirmedOn = formattedConfirmedOn; + this.formattedCreatedOn = formattedCreatedOn; + this.invoiceNumber = invoiceNumber; + this.finalPrice = finalPrice; + this.currencyCode = currencyCode; + this.usedVatPercent = usedVatPercent; + this.vatStatus = vatStatus; + this.items = items; + } + public static ReservationHeader from(ReservationWithPurchaseContext r, Map<Locale, String> datePatternsMap) { return new ReservationHeader( diff --git a/src/main/java/alfio/controller/api/v2/model/ReservationInfo.java b/src/main/java/alfio/controller/api/v2/model/ReservationInfo.java index 89ac9f5387..126f9e9ca6 100644 --- a/src/main/java/alfio/controller/api/v2/model/ReservationInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/ReservationInfo.java @@ -16,15 +16,17 @@ */ package alfio.controller.api.v2.model; +import alfio.controller.api.support.BookingInfoTicket; import alfio.model.BillingDetails; import alfio.model.OrderSummary; +import alfio.model.ReservationMetadata; import alfio.model.SummaryRow.SummaryType; import alfio.model.TicketCategory; import alfio.model.TicketReservation.TicketReservationStatus; +import alfio.model.api.v1.admin.subscription.SubscriptionConfiguration; import alfio.model.subscription.UsageDetails; import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; @@ -32,7 +34,6 @@ import java.util.UUID; import java.util.stream.Collectors; -@AllArgsConstructor @Getter public class ReservationInfo { private final String id; @@ -77,129 +78,67 @@ public class ReservationInfo { private final List<SubscriptionInfo> subscriptionInfos; - - @AllArgsConstructor - @Getter - public static class TicketsByTicketCategory { - private final String name; - private final TicketCategory.TicketAccessType ticketAccessType; - private final List<BookingInfoTicket> tickets; - } - - - @AllArgsConstructor - public static class BookingInfoTicket { - private final String uuid; - private final String firstName; - private final String lastName; - private final String email; - private final String fullName; - private final String userLanguage; - private final boolean assigned; - private final boolean locked; - private final boolean acquired; - private final boolean cancellationEnabled; - private final boolean sendMailEnabled; - private final boolean downloadEnabled; - private final List<AdditionalField> ticketFieldConfiguration; - private final Map<String, String> formattedOnlineCheckInDate; - private final boolean onlineEventStarted; - - public String getEmail() { - return email; - } - - public String getFirstName() { - return firstName; - } - - public String getLastName() { - return lastName; - } - - public String getUuid() { - return uuid; - } - - public String getFullName() { - return fullName; - } - - public boolean isAssigned() { - return assigned; - } - - public boolean isAcquired() { - return acquired; - } - - public String getUserLanguage() { - return userLanguage; - } - - public boolean isLocked() { return locked; } - - public boolean isCancellationEnabled() { - return cancellationEnabled; - } - - public List<AdditionalField> getTicketFieldConfigurationBeforeStandard() { - return ticketFieldConfiguration.stream().filter(AdditionalField::isBeforeStandardFields).collect(Collectors.toList()); - } - - public List<AdditionalField> getTicketFieldConfigurationAfterStandard() { - return ticketFieldConfiguration.stream().filter(tv -> !tv.isBeforeStandardFields()).collect(Collectors.toList()); - } - - public Map<String, String> getFormattedOnlineCheckInDate() { - return formattedOnlineCheckInDate; - } - - public boolean isOnlineEventStarted() { - return onlineEventStarted; - } - - public boolean isSendMailEnabled() { - return sendMailEnabled; - } - - public boolean isDownloadEnabled() { - return downloadEnabled; - } + private final ReservationMetadata metadata; + + public ReservationInfo(String id, + String shortId, + String firstName, + String lastName, + String email, + long validity, + List<TicketsByTicketCategory> ticketsByCategory, + ReservationInfoOrderSummary orderSummary, + TicketReservationStatus status, + boolean validatedBookingInformation, + Map<String, String> formattedExpirationDate, + String invoiceNumber, + boolean invoiceRequested, + boolean invoiceOrReceiptDocumentPresent, + boolean paid, + boolean tokenAcquired, + PaymentProxy paymentProxy, + Boolean addCompanyBillingDetails, + String customerReference, + Boolean skipVatNr, + String billingAddress, + BillingDetails billingDetails, + boolean containsCategoriesLinkedToGroups, + Map<PaymentMethod, PaymentProxyWithParameters> activePaymentMethods, + List<SubscriptionInfo> subscriptionInfos, + ReservationMetadata reservationMetadata) { + this.id = id; + this.shortId = shortId; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.validity = validity; + this.ticketsByCategory = ticketsByCategory; + this.orderSummary = orderSummary; + this.status = status; + this.validatedBookingInformation = validatedBookingInformation; + this.formattedExpirationDate = formattedExpirationDate; + this.invoiceNumber = invoiceNumber; + this.invoiceRequested = invoiceRequested; + this.invoiceOrReceiptDocumentPresent = invoiceOrReceiptDocumentPresent; + this.paid = paid; + this.tokenAcquired = tokenAcquired; + this.paymentProxy = paymentProxy; + this.addCompanyBillingDetails = addCompanyBillingDetails; + this.customerReference = customerReference; + this.skipVatNr = skipVatNr; + this.billingAddress = billingAddress; + this.billingDetails = billingDetails; + this.containsCategoriesLinkedToGroups = containsCategoriesLinkedToGroups; + this.activePaymentMethods = activePaymentMethods; + this.subscriptionInfos = subscriptionInfos; + this.metadata = reservationMetadata; } - @AllArgsConstructor - @Getter - public static class AdditionalField { - private final String name; - private final String value; - private final String type; - private final boolean required; - private final boolean editable; - private final Integer minLength; - private final Integer maxLength; - private final List<String> restrictedValues; - private final List<Field> fields; - private final boolean beforeStandardFields; - private final Map<String, Description> description; - } - @AllArgsConstructor - @Getter - public static class Description { - private final String label; - private final String placeholder; - private final Map<String, String> restrictedValuesDescription; - } - - @AllArgsConstructor - @Getter - public static class Field { - private final int fieldIndex; - private final String fieldValue; + public record TicketsByTicketCategory(String name, TicketCategory.TicketAccessType ticketAccessType, + List<BookingInfoTicket> tickets) { } - @Getter public static class ReservationInfoOrderSummary { @@ -216,7 +155,7 @@ public static class ReservationInfoOrderSummary { public ReservationInfoOrderSummary(OrderSummary orderSummary) { this.summary = orderSummary.getSummary() .stream() - .map(s -> new ReservationInfoOrderSummaryRow(s.getName(), s.getAmount(), s.getPrice(), s.getSubTotal(), s.getType(), s.getTaxPercentage())) + .map(s -> new ReservationInfoOrderSummaryRow(s.name(), s.amount(), s.price(), s.subTotal(), s.type(), s.taxPercentage())) .collect(Collectors.toList()); this.totalPrice = orderSummary.getTotalPrice(); this.free = orderSummary.getFree(); @@ -229,32 +168,22 @@ public ReservationInfoOrderSummary(OrderSummary orderSummary) { } } - @AllArgsConstructor - @Getter - public static class ReservationInfoOrderSummaryRow { - private final String name; - private final int amount; - private final String price; - private final String subTotal; - private final SummaryType type; - private final String taxPercentage; + + + public record ReservationInfoOrderSummaryRow(String name, + int amount, + String price, + String subTotal, + SummaryType type, + String taxPercentage) { } - @AllArgsConstructor - @Getter - public static class SubscriptionInfo { - private final UUID id; - private final String pin; - private final UsageDetails usageDetails; - private final SubscriptionOwner owner; + + public record SubscriptionInfo(UUID id, String pin, UsageDetails usageDetails, SubscriptionOwner owner, SubscriptionConfiguration configuration) { } - @AllArgsConstructor - @Getter - public static class SubscriptionOwner { - private final String firstName; - private final String lastName; - private final String email; + + public record SubscriptionOwner(String firstName, String lastName, String email) { } } diff --git a/src/main/java/alfio/controller/api/v2/model/ReservationPaymentResult.java b/src/main/java/alfio/controller/api/v2/model/ReservationPaymentResult.java index 44830c1f3a..4b72af99bc 100644 --- a/src/main/java/alfio/controller/api/v2/model/ReservationPaymentResult.java +++ b/src/main/java/alfio/controller/api/v2/model/ReservationPaymentResult.java @@ -16,10 +16,8 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class ReservationPaymentResult { private final boolean success; @@ -27,4 +25,12 @@ public class ReservationPaymentResult { private final String redirectUrl; private final boolean failure; private final String gatewayIdOrNull; + + public ReservationPaymentResult(boolean success, boolean redirect, String redirectUrl, boolean failure, String gatewayIdOrNull) { + this.success = success; + this.redirect = redirect; + this.redirectUrl = redirectUrl; + this.failure = failure; + this.gatewayIdOrNull = gatewayIdOrNull; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/ReservationStatusInfo.java b/src/main/java/alfio/controller/api/v2/model/ReservationStatusInfo.java index 4d462ef6a2..aa4ac9b848 100644 --- a/src/main/java/alfio/controller/api/v2/model/ReservationStatusInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/ReservationStatusInfo.java @@ -17,13 +17,16 @@ package alfio.controller.api.v2.model; import alfio.model.TicketReservation; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class ReservationStatusInfo { private final TicketReservation.TicketReservationStatus status; private final boolean validatedBookingInformation; + + public ReservationStatusInfo(TicketReservation.TicketReservationStatus status, boolean validatedBookingInformation) { + this.status = status; + this.validatedBookingInformation = validatedBookingInformation; + } } diff --git a/src/main/java/alfio/controller/api/v2/model/SubscriptionDescriptorWithAdditionalInfo.java b/src/main/java/alfio/controller/api/v2/model/SubscriptionDescriptorWithAdditionalInfo.java index 1aaef04be6..8843f382a1 100644 --- a/src/main/java/alfio/controller/api/v2/model/SubscriptionDescriptorWithAdditionalInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/SubscriptionDescriptorWithAdditionalInfo.java @@ -22,17 +22,16 @@ import alfio.model.subscription.SubscriptionDescriptor; import alfio.util.MonetaryUtil; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; import java.util.List; import java.util.Map; -@AllArgsConstructor public class SubscriptionDescriptorWithAdditionalInfo implements ApiPurchaseContext { private final SubscriptionDescriptor subscriptionDescriptor; private final InvoicingConfiguration invoicingConfiguration; private final AnalyticsConfiguration analyticsConfiguration; private final EventWithAdditionalInfo.CaptchaConfiguration captchaConfiguration; + private final EmbeddingConfiguration embeddingConfiguration; //payment related information private final String bankAccount; @@ -50,6 +49,40 @@ public class SubscriptionDescriptorWithAdditionalInfo implements ApiPurchaseCont private final Map<String, String> formattedValidTo; private final Integer numAvailable; + public SubscriptionDescriptorWithAdditionalInfo(SubscriptionDescriptor subscriptionDescriptor, + InvoicingConfiguration invoicingConfiguration, + AnalyticsConfiguration analyticsConfiguration, + EventWithAdditionalInfo.CaptchaConfiguration captchaConfiguration, + EmbeddingConfiguration embeddingConfiguration, + String bankAccount, + List<String> bankAccountOwner, + String organizationEmail, + String organizationName, + DatesWithTimeZoneOffset salePeriod, + Map<String, String> formattedOnSaleFrom, + Map<String, String> formattedOnSaleTo, + String timeZone, + Map<String, String> formattedValidFrom, + Map<String, String> formattedValidTo, + Integer numAvailable) { + this.subscriptionDescriptor = subscriptionDescriptor; + this.invoicingConfiguration = invoicingConfiguration; + this.analyticsConfiguration = analyticsConfiguration; + this.captchaConfiguration = captchaConfiguration; + this.embeddingConfiguration = embeddingConfiguration; + this.bankAccount = bankAccount; + this.bankAccountOwner = bankAccountOwner; + this.organizationEmail = organizationEmail; + this.organizationName = organizationName; + this.salePeriod = salePeriod; + this.formattedOnSaleFrom = formattedOnSaleFrom; + this.formattedOnSaleTo = formattedOnSaleTo; + this.timeZone = timeZone; + this.formattedValidFrom = formattedValidFrom; + this.formattedValidTo = formattedValidTo; + this.numAvailable = numAvailable; + } + @Override public InvoicingConfiguration getInvoicingConfiguration() { return invoicingConfiguration; @@ -187,6 +220,17 @@ public Integer getMaxEntries() { return subscriptionDescriptor.getMaxEntries() > 0 ? subscriptionDescriptor.getMaxEntries() : null; } + @Override + public OfflinePaymentConfiguration getOfflinePaymentConfiguration() { + // offline payment is not supported for subscriptions + return null; + } + + @Override + public EmbeddingConfiguration getEmbeddingConfiguration() { + return embeddingConfiguration; + } + @Override public boolean isCanApplySubscriptions() { return false;//cannot buy a subscription with another subscription diff --git a/src/main/java/alfio/controller/api/v2/model/TicketCategory.java b/src/main/java/alfio/controller/api/v2/model/TicketCategory.java index 6f63f9bb1a..8ef45d5fff 100644 --- a/src/main/java/alfio/controller/api/v2/model/TicketCategory.java +++ b/src/main/java/alfio/controller/api/v2/model/TicketCategory.java @@ -49,13 +49,15 @@ public class TicketCategory { private final boolean accessRestricted; private final boolean soldOutOrLimitReached; private final Integer availableTickets; + private final boolean displayTaxInformation; // public TicketCategory(SaleableTicketCategory saleableTicketCategory, Map<String, String> description, Map<String, String> formattedInception, Map<String, String> formattedExpiration, - boolean displayTicketsLeft) { + boolean displayTicketsLeft, + boolean displayTaxInformation) { this.description = description; this.id = saleableTicketCategory.getId(); @@ -82,5 +84,7 @@ public TicketCategory(SaleableTicketCategory saleableTicketCategory, // this.availableTickets = displayTicketsLeft && saleableTicketCategory.isBounded() ? saleableTicketCategory.getAvailableTickets() : null; this.ordinal = saleableTicketCategory.isAccessRestricted() ? -1 : saleableTicketCategory.getOrdinal(); + + this.displayTaxInformation = displayTaxInformation; } } diff --git a/src/main/java/alfio/controller/api/v2/model/TicketInfo.java b/src/main/java/alfio/controller/api/v2/model/TicketInfo.java index d3142a52a2..538765c182 100644 --- a/src/main/java/alfio/controller/api/v2/model/TicketInfo.java +++ b/src/main/java/alfio/controller/api/v2/model/TicketInfo.java @@ -16,13 +16,11 @@ */ package alfio.controller.api.v2.model; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Map; @Getter -@AllArgsConstructor public class TicketInfo implements DateValidity { private final String fullName; @@ -43,5 +41,35 @@ public class TicketInfo implements DateValidity { private final Map<String, String> formattedBeginTime; //the hour/minute component private final Map<String, String> formattedEndDate; private final Map<String, String> formattedEndTime; + + public TicketInfo(String fullName, + String email, + String uuid, + String ticketCategoryName, + String reservationFullName, + String reservationId, + boolean deskPaymentRequired, + String timeZone, + DatesWithTimeZoneOffset datesWithOffset, + boolean sameDay, + Map<String, String> formattedBeginDate, + Map<String, String> formattedBeginTime, + Map<String, String> formattedEndDate, + Map<String, String> formattedEndTime) { + this.fullName = fullName; + this.email = email; + this.uuid = uuid; + this.ticketCategoryName = ticketCategoryName; + this.reservationFullName = reservationFullName; + this.reservationId = reservationId; + this.deskPaymentRequired = deskPaymentRequired; + this.timeZone = timeZone; + this.datesWithOffset = datesWithOffset; + this.sameDay = sameDay; + this.formattedBeginDate = formattedBeginDate; + this.formattedBeginTime = formattedBeginTime; + this.formattedEndDate = formattedEndDate; + this.formattedEndTime = formattedEndTime; + } // } diff --git a/src/main/java/alfio/controller/api/v2/model/WalletConfiguration.java b/src/main/java/alfio/controller/api/v2/model/WalletConfiguration.java new file mode 100644 index 0000000000..cff3423839 --- /dev/null +++ b/src/main/java/alfio/controller/api/v2/model/WalletConfiguration.java @@ -0,0 +1,35 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v2.model; + +public class WalletConfiguration { + private final boolean gWalletEnabled; + private final boolean passEnabled; + + public WalletConfiguration(boolean gWalletEnabled, boolean passEnabled) { + this.gWalletEnabled = gWalletEnabled; + this.passEnabled = passEnabled; + } + + public boolean isgWalletEnabled() { + return gWalletEnabled; + } + + public boolean isPassEnabled() { + return passEnabled; + } +} diff --git a/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java index 9b1f018593..cd213224b0 100644 --- a/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/EventApiV2Controller.java @@ -38,7 +38,6 @@ import alfio.model.system.ConfigurationKeys; import alfio.repository.*; import alfio.util.*; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; @@ -69,15 +68,14 @@ @RestController @RequestMapping("/api/v2/public/") -@AllArgsConstructor public class EventApiV2Controller { + private static final String TICKET_CATEGORY_DATE_FORMAT = "common.ticket-category.date-format"; private final EventManager eventManager; private final EventRepository eventRepository; private final ConfigurationManager configurationManager; private final EventDescriptionRepository eventDescriptionRepository; private final TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository; - private final PaymentManager paymentManager; private final MessageSourceManager messageSourceManager; private final AdditionalServiceRepository additionalServiceRepository; private final AdditionalServiceTextRepository additionalServiceTextRepository; @@ -94,6 +92,48 @@ public class EventApiV2Controller { private final ExtensionManager extensionManager; private final ClockProvider clockProvider; + public EventApiV2Controller(EventManager eventManager, + EventRepository eventRepository, + ConfigurationManager configurationManager, + EventDescriptionRepository eventDescriptionRepository, + TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository, + MessageSourceManager messageSourceManager, + AdditionalServiceRepository additionalServiceRepository, + AdditionalServiceTextRepository additionalServiceTextRepository, + WaitingQueueManager waitingQueueManager, + I18nManager i18nManager, + TicketCategoryRepository ticketCategoryRepository, + TicketRepository ticketRepository, + TicketReservationManager ticketReservationManager, + PromoCodeDiscountRepository promoCodeRepository, + EventStatisticsManager eventStatisticsManager, + RecaptchaService recaptchaService, + PromoCodeRequestManager promoCodeRequestManager, + EventLoader eventLoader, + ExtensionManager extensionManager, + ClockProvider clockProvider) { + this.eventManager = eventManager; + this.eventRepository = eventRepository; + this.configurationManager = configurationManager; + this.eventDescriptionRepository = eventDescriptionRepository; + this.ticketCategoryDescriptionRepository = ticketCategoryDescriptionRepository; + this.messageSourceManager = messageSourceManager; + this.additionalServiceRepository = additionalServiceRepository; + this.additionalServiceTextRepository = additionalServiceTextRepository; + this.waitingQueueManager = waitingQueueManager; + this.i18nManager = i18nManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketRepository = ticketRepository; + this.ticketReservationManager = ticketReservationManager; + this.promoCodeRepository = promoCodeRepository; + this.eventStatisticsManager = eventStatisticsManager; + this.recaptchaService = recaptchaService; + this.promoCodeRequestManager = promoCodeRequestManager; + this.eventLoader = eventLoader; + this.extensionManager = extensionManager; + this.clockProvider = clockProvider; + } + @GetMapping("events") public ResponseEntity<List<BasicEventInfo>> listEvents(SearchOptions searchOptions) { @@ -108,9 +148,9 @@ public ResponseEntity<List<BasicEventInfo>> listEvents(SearchOptions searchOptio return new BasicEventInfo(e.getShortName(), e.getFileBlobId(), e.getTitle(), e.getFormat(), e.getLocation(), e.getTimeZone(), DatesWithTimeZoneOffset.fromEvent(e), e.getSameDay(), formattedDates.beginDate, formattedDates.beginTime, formattedDates.endDate, formattedDates.endTime, - e.getContentLanguages().stream().map(cl -> new Language(cl.getLocale().getLanguage(), cl.getDisplayLanguage())).collect(toList())); + e.getContentLanguages().stream().map(cl -> new Language(cl.getLocale().getLanguage(), cl.getDisplayLanguage())).toList()); }) - .collect(Collectors.toList()); + .toList(); return new ResponseEntity<>(events, getCorsHeaders(), HttpStatus.OK); } @@ -158,8 +198,8 @@ public ResponseEntity<ItemsByCategory> getTicketCategories(@PathVariable("eventN var ticketCategories = ticketCategoryRepository.findAllTicketCategories(event.getId()); List<SaleableTicketCategory> saleableTicketCategories = ticketCategories.stream() - .filter((c) -> !c.isAccessRestricted() || shouldDisplayRestrictedCategory(specialCode, c, promoCodeDiscount)) - .map((category) -> { + .filter(c -> !c.isAccessRestricted() || shouldDisplayRestrictedCategory(specialCode, c, promoCodeDiscount)) + .map(category -> { int maxTickets = getMaxAmountOfTicketsPerReservation(configurations, ticketCategoryLevelConfiguration, category.getId()); PromoCodeDiscount filteredPromoCode = promoCodeDiscount.filter(promoCode -> shouldApplyDiscount(promoCode, category)).orElse(null); if (specialCode.isPresent()) { @@ -171,23 +211,24 @@ public ResponseEntity<ItemsByCategory> getTicketCategories(@PathVariable("eventN now, event, ticketReservationManager.countAvailableTickets(event, category), maxTickets, filteredPromoCode); }) - .collect(Collectors.toList()); + .toList(); - var valid = saleableTicketCategories.stream().filter(tc -> !tc.getExpired()).collect(Collectors.toList()); + var valid = saleableTicketCategories.stream().filter(tc -> !tc.getExpired()).toList(); // - var ticketCategoryIds = valid.stream().map(SaleableTicketCategory::getId).collect(Collectors.toList()); + var ticketCategoryIds = valid.stream().map(SaleableTicketCategory::getId).toList(); var ticketCategoryDescriptions = ticketCategoryDescriptionRepository.descriptionsByTicketCategory(ticketCategoryIds); + var categoriesNoTax = configurationManager.getCategoriesWithNoTaxes(ticketCategoryIds); boolean displayTicketsLeft = configurations.get(DISPLAY_TICKETS_LEFT_INDICATOR).getValueAsBooleanOrDefault(); var categoriesByExpiredFlag = saleableTicketCategories.stream() .map(stc -> { - var description = Formatters.applyCommonMark(ticketCategoryDescriptions.getOrDefault(stc.getId(), Collections.emptyMap())); - var expiration = Formatters.getFormattedDate(event, stc.getZonedExpiration(), "common.ticket-category.date-format", messageSource); - var inception = Formatters.getFormattedDate(event, stc.getZonedInception(), "common.ticket-category.date-format", messageSource); - return new TicketCategory(stc, description, inception, expiration, displayTicketsLeft && !stc.isAccessRestricted()); + var description = Formatters.applyCommonMark(ticketCategoryDescriptions.getOrDefault(stc.getId(), Collections.emptyMap()), messageSource); + var expiration = Formatters.getFormattedDate(event, stc.getZonedExpiration(), TICKET_CATEGORY_DATE_FORMAT, messageSource); + var inception = Formatters.getFormattedDate(event, stc.getZonedInception(), TICKET_CATEGORY_DATE_FORMAT, messageSource); + return new TicketCategory(stc, description, inception, expiration, displayTicketsLeft && !stc.isAccessRestricted(), !categoriesNoTax.contains(stc.getId())); }) .sorted(Comparator.comparingInt(TicketCategory::getOrdinal)) .collect(partitioningBy(TicketCategory::isExpired)); @@ -201,33 +242,32 @@ public ResponseEntity<ItemsByCategory> getTicketCategories(@PathVariable("eventN var saleableAdditionalServices = additionalServiceRepository.loadAllForEvent(event.getId()) .stream() .map(as -> new SaleableAdditionalService(event, as, promoCode.orElse(null))) - .filter(SaleableAdditionalService::isNotExpired) - .collect(Collectors.toList()); + .filter(SaleableAdditionalService::isNotExpired).toList(); // will be used for fetching descriptions and titles for all the languages - var saleableAdditionalServicesIds = saleableAdditionalServices.stream().map(SaleableAdditionalService::getId).collect(Collectors.toList()); + var saleableAdditionalServicesIds = saleableAdditionalServices.stream().map(SaleableAdditionalService::getId).toList(); var additionalServiceTexts = additionalServiceTextRepository.getDescriptionsByAdditionalServiceIds(saleableAdditionalServicesIds); var additionalServicesRes = saleableAdditionalServices.stream().map(as -> { - var expiration = Formatters.getFormattedDate(event, as.getZonedExpiration(), "common.ticket-category.date-format", messageSource); - var inception = Formatters.getFormattedDate(event, as.getZonedInception(), "common.ticket-category.date-format", messageSource); + var expiration = Formatters.getFormattedDate(event, as.getZonedExpiration(), TICKET_CATEGORY_DATE_FORMAT, messageSource); + var inception = Formatters.getFormattedDate(event, as.getZonedInception(), TICKET_CATEGORY_DATE_FORMAT, messageSource); var title = additionalServiceTexts.getOrDefault(as.getId(), Collections.emptyMap()).getOrDefault(AdditionalServiceText.TextType.TITLE, Collections.emptyMap()); - var description = Formatters.applyCommonMark(additionalServiceTexts.getOrDefault(as.getId(), Collections.emptyMap()).getOrDefault(AdditionalServiceText.TextType.DESCRIPTION, Collections.emptyMap())); + var description = Formatters.applyCommonMark(additionalServiceTexts.getOrDefault(as.getId(), Collections.emptyMap()).getOrDefault(AdditionalServiceText.TextType.DESCRIPTION, Collections.emptyMap()), messageSource); return new AdditionalService(as.getId(), as.getType(), as.getSupplementPolicy(), as.isFixPrice(), as.getAvailableQuantity(), as.getMaxQtyPerOrder(), as.getFree(), as.getFormattedFinalPrice(), as.getSupportsDiscount(), as.getDiscountedPrice(), as.getVatApplies(), as.getVatIncluded(), as.getVatPercentage().toString(), as.isExpired(), as.getSaleInFuture(), inception, expiration, title, description); - }).collect(Collectors.toList()); + }).toList(); // // waiting queue parameters boolean displayWaitingQueueForm = EventUtil.displayWaitingQueueForm(event, saleableTicketCategories, configurationManager, eventStatisticsManager.noSeatsAvailable()); boolean preSales = EventUtil.isPreSales(event, saleableTicketCategories); Predicate<SaleableTicketCategory> waitingQueueTargetCategory = tc -> !tc.getExpired() && !tc.isBounded(); - List<SaleableTicketCategory> unboundedCategories = saleableTicketCategories.stream().filter(waitingQueueTargetCategory).collect(Collectors.toList()); - var tcForWaitingList = unboundedCategories.stream().map(stc -> new ItemsByCategory.TicketCategoryForWaitingList(stc.getId(), stc.getName())).collect(toList()); + List<SaleableTicketCategory> unboundedCategories = saleableTicketCategories.stream().filter(waitingQueueTargetCategory).toList(); + var tcForWaitingList = unboundedCategories.stream().map(stc -> new ItemsByCategory.TicketCategoryForWaitingList(stc.getId(), stc.getName())).toList(); // var activeCategories = categoriesByExpiredFlag.get(false); var expiredCategories = configurations.get(DISPLAY_EXPIRED_CATEGORIES).getValueAsBooleanOrDefault() ? categoriesByExpiredFlag.get(true) : List.<TicketCategory>of(); @@ -378,7 +418,7 @@ public ResponseEntity<DynamicDiscount> checkDiscount(@PathVariable("eventName") return new DynamicDiscount(formattedDiscount, d.getDiscountType(), formatDynamicCodeMessage(event, d)); }); }) - .filter(d -> d.getDiscountType() != PromoCodeDiscount.DiscountType.NONE) + .filter(d -> d.discountType() != PromoCodeDiscount.DiscountType.NONE) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.noContent().build()); } @@ -443,21 +483,20 @@ private Map<String, String> formatDynamicCodeMessage(Event event, PromoCodeDisco Map<String, String> res = new HashMap<>(); String code; String amount; - switch(promoCodeDiscount.getDiscountType()) { - case PERCENTAGE: + switch (promoCodeDiscount.getDiscountType()) { + case PERCENTAGE -> { code = "reservation.dynamic.discount.confirmation.percentage.message"; amount = String.valueOf(promoCodeDiscount.getDiscountAmount()); - break; - case FIXED_AMOUNT: + } + case FIXED_AMOUNT -> { amount = event.getCurrency() + " " + MonetaryUtil.formatCents(promoCodeDiscount.getDiscountAmount(), event.getCurrency()); code = "reservation.dynamic.discount.confirmation.fix-per-ticket.message"; - break; - case FIXED_AMOUNT_RESERVATION: + } + case FIXED_AMOUNT_RESERVATION -> { amount = event.getCurrency() + " " + MonetaryUtil.formatCents(promoCodeDiscount.getDiscountAmount(), event.getCurrency()); code = "reservation.dynamic.discount.confirmation.fix-per-reservation.message"; - break; - default: - throw new IllegalStateException("Unexpected discount code type"); + } + default -> throw new IllegalStateException("Unexpected discount code type"); } for (ContentLanguage cl : event.getContentLanguages()) { diff --git a/src/main/java/alfio/controller/api/v2/user/PollApiController.java b/src/main/java/alfio/controller/api/v2/user/PollApiController.java index 684d12e6b8..ecb50beb54 100644 --- a/src/main/java/alfio/controller/api/v2/user/PollApiController.java +++ b/src/main/java/alfio/controller/api/v2/user/PollApiController.java @@ -21,7 +21,6 @@ import alfio.manager.support.response.ValidatedResponse; import alfio.model.poll.Poll; import alfio.model.poll.PollWithOptions; -import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -30,11 +29,14 @@ @RestController @RequestMapping("/api/v2/public/event/{eventName}/poll") -@RequiredArgsConstructor public class PollApiController { private final PollManager pollManager; + public PollApiController(PollManager pollManager) { + this.pollManager = pollManager; + } + @GetMapping("") ResponseEntity<ValidatedResponse<List<Poll>>> getAll(@PathVariable("eventName") String eventName, @RequestParam("pin") String pin) { var result = pollManager.getActiveForEvent(eventName, pin); diff --git a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java index fdf7f9129e..b5a578b198 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -16,13 +16,13 @@ */ package alfio.controller.api.v2.user; +import alfio.controller.api.support.BookingInfoTicketLoader; import alfio.controller.api.support.TicketHelper; import alfio.controller.api.v2.model.PaymentProxyWithParameters; import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.model.ReservationInfo.TicketsByTicketCategory; import alfio.controller.api.v2.model.ReservationPaymentResult; import alfio.controller.api.v2.model.ReservationStatusInfo; -import alfio.controller.api.v2.user.support.BookingInfoTicketLoader; import alfio.controller.api.v2.user.support.ReservationAccessDenied; import alfio.controller.form.ContactAndTicketsForm; import alfio.controller.form.PaymentForm; @@ -39,26 +39,23 @@ import alfio.manager.user.PublicUserManager; import alfio.model.*; import alfio.model.PurchaseContext.PurchaseContextType; -import alfio.model.subscription.Subscription; -import alfio.model.subscription.SubscriptionUsageExceeded; -import alfio.model.subscription.SubscriptionUsageExceededForEvent; +import alfio.model.metadata.SubscriptionMetadata; import alfio.model.subscription.UsageDetails; import alfio.model.system.ConfigurationKeys; import alfio.model.transaction.*; import alfio.repository.*; import alfio.util.*; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; @@ -76,14 +73,15 @@ import static alfio.model.system.ConfigurationKeys.ENABLE_ITALY_E_INVOICING; import static alfio.model.system.ConfigurationKeys.FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION; +import static java.util.Objects.requireNonNullElseGet; import static java.util.stream.Collectors.toMap; @RestController -@AllArgsConstructor @RequestMapping("/api/v2/public/") -@Log4j2 public class ReservationApiV2Controller { + private static final Logger log = LoggerFactory.getLogger(ReservationApiV2Controller.class); + private final EventManager eventManager; private final EventRepository eventRepository; private final TicketReservationManager ticketReservationManager; @@ -105,6 +103,53 @@ public class ReservationApiV2Controller { private final TicketRepository ticketRepository; private final PublicUserManager publicUserManager; private final ReverseChargeManager reverseChargeManager; + private final TicketCategoryRepository ticketCategoryRepository; + + public ReservationApiV2Controller(EventManager eventManager, + EventRepository eventRepository, + TicketReservationManager ticketReservationManager, + TicketReservationRepository ticketReservationRepository, + TicketFieldRepository ticketFieldRepository, + MessageSourceManager messageSourceManager, + ConfigurationManager configurationManager, + PaymentManager paymentManager, + FileUploadManager fileUploadManager, + TemplateManager templateManager, + ExtensionManager extensionManager, + TicketHelper ticketHelper, + EuVatChecker vatChecker, + RecaptchaService recaptchaService, + BookingInfoTicketLoader bookingInfoTicketLoader, + BillingDocumentManager billingDocumentManager, + PurchaseContextManager purchaseContextManager, + SubscriptionRepository subscriptionRepository, + TicketRepository ticketRepository, + PublicUserManager publicUserManager, + ReverseChargeManager reverseChargeManager, + TicketCategoryRepository ticketCategoryRepository) { + this.eventManager = eventManager; + this.eventRepository = eventRepository; + this.ticketReservationManager = ticketReservationManager; + this.ticketReservationRepository = ticketReservationRepository; + this.ticketFieldRepository = ticketFieldRepository; + this.messageSourceManager = messageSourceManager; + this.configurationManager = configurationManager; + this.paymentManager = paymentManager; + this.fileUploadManager = fileUploadManager; + this.templateManager = templateManager; + this.extensionManager = extensionManager; + this.ticketHelper = ticketHelper; + this.vatChecker = vatChecker; + this.recaptchaService = recaptchaService; + this.bookingInfoTicketLoader = bookingInfoTicketLoader; + this.billingDocumentManager = billingDocumentManager; + this.purchaseContextManager = purchaseContextManager; + this.subscriptionRepository = subscriptionRepository; + this.ticketRepository = ticketRepository; + this.publicUserManager = publicUserManager; + this.reverseChargeManager = reverseChargeManager; + this.ticketCategoryRepository = ticketCategoryRepository; + } /** * Note: now it will return for any states of the reservation. @@ -163,10 +208,7 @@ public ResponseEntity<ReservationInfo> getReservationInfo(@PathVariable("reserva var additionalInfo = ticketReservationRepository.getAdditionalInfo(reservationId); - var shortReservationId = ticketReservationManager.getShortReservationID(purchaseContext, reservation); - var italianInvoicing = additionalInfo.getInvoicingAdditionalInfo().getItalianEInvoicing() == null ? - new TicketReservationInvoicingAdditionalInfo.ItalianEInvoicing(null, null, null, null, false) : - additionalInfo.getInvoicingAdditionalInfo().getItalianEInvoicing(); + var shortReservationId = configurationManager.getShortReservationID(purchaseContext, reservation); // @@ -182,14 +224,16 @@ public ResponseEntity<ReservationInfo> getReservationInfo(@PathVariable("reserva List<ReservationInfo.SubscriptionInfo> subscriptionInfos = null; if (purchaseContext.ofType(PurchaseContextType.subscription)) { subscriptionInfos = subscriptionRepository.findSubscriptionsByReservationId(reservationId).stream() - .limit(1) // since we support only one subscription for now, it make sense to limit the result to avoid N+1 + .limit(1) // since we support only one subscription for now, it makes sense to limit the result to avoid N+1 .map(s -> { int usageCount = ticketRepository.countSubscriptionUsage(s.getId(), null); + var metadata = requireNonNullElseGet(subscriptionRepository.getSubscriptionMetadata(s.getId()), SubscriptionMetadata::empty); return new ReservationInfo.SubscriptionInfo( s.getStatus() == AllocationStatus.ACQUIRED ? s.getId() : null, s.getStatus() == AllocationStatus.ACQUIRED ? s.getPin() : null, UsageDetails.fromSubscription(s, usageCount), - new ReservationInfo.SubscriptionOwner(s.getFirstName(), s.getLastName(), s.getEmail())); + new ReservationInfo.SubscriptionOwner(s.getFirstName(), s.getLastName(), s.getEmail()), + metadata.getConfiguration()); }) .collect(Collectors.toList()); } @@ -215,7 +259,8 @@ public ResponseEntity<ReservationInfo> getReservationInfo(@PathVariable("reserva // containsCategoriesLinkedToGroups, getActivePaymentMethods(purchaseContext, ticketsByCategory.keySet(), orderSummary, reservationId), - subscriptionInfos + subscriptionInfos, + ticketReservationRepository.getMetadata(reservationId) )); })); @@ -299,7 +344,7 @@ public ResponseEntity<ValidatedResponse<ReservationPaymentResult>> confirmOvervi return buildReservationPaymentStatus(bindingResult); } - if(isCaptchaInvalid(reservationCost.getPriceWithVAT(), paymentForm.getPaymentProxy(), paymentForm.getCaptcha(), request, event)) { + if(isCaptchaInvalid(reservationCost.priceWithVAT(), paymentForm.getPaymentProxy(), paymentForm.getCaptcha(), request, event)) { log.debug("captcha validation failed."); bindingResult.reject(ErrorsCode.STEP_2_CAPTCHA_VALIDATION_FAILED); } @@ -321,7 +366,7 @@ public ResponseEntity<ValidatedResponse<ReservationPaymentResult>> confirmOvervi paymentToken = paymentManager.buildPaymentToken(paymentForm.getGatewayToken(), paymentForm.getPaymentProxy(), new PaymentContext(event, reservationId)); } - PaymentSpecification spec = new PaymentSpecification(reservationId, paymentToken, reservationCost.getPriceWithVAT(), + PaymentSpecification spec = new PaymentSpecification(reservationId, paymentToken, reservationCost.priceWithVAT(), event, reservation.getEmail(), customerName, reservation.getBillingAddress(), reservation.getCustomerReference(), locale, reservation.isInvoiceRequested(), !reservation.isDirectAssignmentRequested(), orderSummary, reservation.getVatCountryCode(), reservation.getVatNr(), reservation.getVatStatus(), @@ -384,10 +429,10 @@ public ResponseEntity<ValidatedResponse<Boolean>> validateToOverview(@PathVariab boolean invoiceOnly = configurationManager.isInvoiceOnly(purchaseContext); - if(invoiceOnly && reservationCost.getPriceWithVAT() > 0) { + if(invoiceOnly && reservationCost.priceWithVAT() > 0) { //override, that's why we save it contactAndTicketsForm.setInvoiceRequested(true); - } else if (reservationCost.getPriceWithVAT() == 0) { + } else if (reservationCost.priceWithVAT() == 0) { contactAndTicketsForm.setInvoiceRequested(false); } @@ -395,10 +440,20 @@ public ResponseEntity<ValidatedResponse<Boolean>> validateToOverview(@PathVariab ticketReservationRepository.resetVat(reservationId, contactAndTicketsForm.isInvoiceRequested(), purchaseContext.getVatStatus(), - reservation.getSrcPriceCts(), reservationCost.getPriceWithVAT(), reservationCost.getVAT(), Math.abs(reservationCost.getDiscount()), reservation.getCurrencyCode()); - if(contactAndTicketsForm.isBusiness()) { + reservation.getSrcPriceCts(), reservationCost.priceWithVAT(), reservationCost.VAT(), Math.abs(reservationCost.discount()), reservation.getCurrencyCode()); + + var optionalCustomTaxPolicy = extensionManager.handleCustomTaxPolicy(purchaseContext, reservationId, contactAndTicketsForm, reservationCost); + if (optionalCustomTaxPolicy.isPresent()) { + log.debug("Custom tax policy returned for reservation {}. Applying it.", reservationId); + reverseChargeManager.applyCustomTaxPolicy( + purchaseContext, + optionalCustomTaxPolicy.get(), + reservationId, + contactAndTicketsForm, + bindingResult); + } else if(reservationCost.priceWithVAT() > 0 && (contactAndTicketsForm.isBusiness() || configurationManager.noTaxesFlagDefinedFor(ticketCategoryRepository.findCategoriesInReservation(reservationId)))) { reverseChargeManager.checkAndApplyVATRules(purchaseContext, reservationId, contactAndTicketsForm, bindingResult); - } else if(reservationCost.getPriceWithVAT() > 0) { + } else if(reservationCost.priceWithVAT() > 0) { reverseChargeManager.resetVat(purchaseContext, reservationId); } diff --git a/src/main/java/alfio/controller/api/v2/user/SubscriptionsApiController.java b/src/main/java/alfio/controller/api/v2/user/SubscriptionsApiController.java index 8ae1e41153..6994614f40 100644 --- a/src/main/java/alfio/controller/api/v2/user/SubscriptionsApiController.java +++ b/src/main/java/alfio/controller/api/v2/user/SubscriptionsApiController.java @@ -32,7 +32,6 @@ import alfio.repository.user.OrganizationRepository; import alfio.util.ClockProvider; import alfio.util.MonetaryUtil; -import lombok.AllArgsConstructor; import org.joda.money.CurrencyUnit; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; @@ -47,15 +46,14 @@ import java.util.stream.Collectors; import static alfio.model.PriceContainer.VatStatus.isVatIncluded; -import static alfio.model.system.ConfigurationKeys.BANK_ACCOUNT_NR; -import static alfio.model.system.ConfigurationKeys.BANK_ACCOUNT_OWNER; +import static alfio.model.system.ConfigurationKeys.*; import static java.util.stream.Collectors.toList; @RestController @RequestMapping("/api/v2/public/") -@AllArgsConstructor public class SubscriptionsApiController { + private static final String DATE_FORMAT_KEY = "common.event.date-format"; private final SubscriptionManager subscriptionManager; private final I18nManager i18nManager; private final TicketReservationManager reservationManager; @@ -64,11 +62,25 @@ public class SubscriptionsApiController { private final MessageSourceManager messageSourceManager; private final ClockProvider clockProvider; + public SubscriptionsApiController(SubscriptionManager subscriptionManager, + I18nManager i18nManager, + TicketReservationManager reservationManager, + ConfigurationManager configurationManager, + OrganizationRepository organizationRepository, + MessageSourceManager messageSourceManager, + ClockProvider clockProvider) { + this.subscriptionManager = subscriptionManager; + this.i18nManager = i18nManager; + this.reservationManager = reservationManager; + this.configurationManager = configurationManager; + this.organizationRepository = organizationRepository; + this.messageSourceManager = messageSourceManager; + this.clockProvider = clockProvider; + } + @GetMapping("subscriptions") public ResponseEntity<List<BasicSubscriptionDescriptorInfo>> listSubscriptions(SearchOptions searchOptions) { - var contentLanguages = i18nManager.getAvailableLanguages(); - var now = ZonedDateTime.now(ClockProvider.clock()); var activeSubscriptions = subscriptionManager.getActivePublicSubscriptionsDescriptor(now, searchOptions) .stream() @@ -100,10 +112,10 @@ private static BasicSubscriptionDescriptorInfo subscriptionDescriptorMapper(Subs currencyDescriptor, s.getVat(), isVatIncluded(s.getVatStatus()), - Formatters.getFormattedDate(s, s.getOnSaleFrom(), "common.event.date-format", messageSource), - Formatters.getFormattedDate(s, s.getOnSaleTo(), "common.event.date-format", messageSource), - Formatters.getFormattedDate(s, s.getValidityFrom(), "common.event.date-format", messageSource), - Formatters.getFormattedDate(s, s.getValidityTo(), "common.event.date-format", messageSource), + Formatters.getFormattedDate(s, s.getOnSaleFrom(), DATE_FORMAT_KEY, messageSource), + Formatters.getFormattedDate(s, s.getOnSaleTo(), DATE_FORMAT_KEY, messageSource), + Formatters.getFormattedDate(s, s.getValidityFrom(), DATE_FORMAT_KEY, messageSource), + Formatters.getFormattedDate(s, s.getValidityTo(), DATE_FORMAT_KEY, messageSource), s.getContentLanguages().stream().map(cl -> new Language(cl.getLocale().getLanguage(), cl.getDisplayLanguage())).collect(toList()) ); } @@ -133,16 +145,17 @@ public ResponseEntity<SubscriptionDescriptorWithAdditionalInfo> getSubscriptionI invoicingInfo, analyticsConf, captchaConf, + new EmbeddingConfiguration(configurationsValues.get(EMBED_POST_MESSAGE_ORIGIN).getValueOrNull()), bankAccount, bankAccountOwner, orgContact.getEmail(), orgContact.getName(), DatesWithTimeZoneOffset.fromDates(s.getOnSaleFrom(), s.getOnSaleTo()), - Formatters.getFormattedDate(s, s.getOnSaleFrom(), "common.event.date-format", messageSource), - Formatters.getFormattedDate(s, s.getOnSaleTo(), "common.event.date-format", messageSource), + Formatters.getFormattedDate(s, s.getOnSaleFrom(), DATE_FORMAT_KEY, messageSource), + Formatters.getFormattedDate(s, s.getOnSaleTo(), DATE_FORMAT_KEY, messageSource), s.getZoneId().toString(), - Formatters.getFormattedDate(s, s.getValidityFrom(), "common.event.date-format", messageSource), - Formatters.getFormattedDate(s, s.getValidityTo(), "common.event.date-format", messageSource), + Formatters.getFormattedDate(s, s.getValidityFrom(), DATE_FORMAT_KEY, messageSource), + Formatters.getFormattedDate(s, s.getValidityTo(), DATE_FORMAT_KEY, messageSource), available); }) .map(ResponseEntity::ok) diff --git a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java index 72f0214499..21464c8bb4 100644 --- a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java @@ -16,21 +16,19 @@ */ package alfio.controller.api.v2.user; +import alfio.controller.api.support.BookingInfoTicketLoader; import alfio.controller.api.support.TicketHelper; import alfio.controller.api.v2.model.DatesWithTimeZoneOffset; import alfio.controller.api.v2.model.OnlineCheckInInfo; import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.model.TicketInfo; -import alfio.controller.api.v2.user.support.BookingInfoTicketLoader; import alfio.controller.form.UpdateTicketOwnerForm; import alfio.controller.support.Formatters; import alfio.controller.support.TemplateProcessor; -import alfio.manager.ExtensionManager; -import alfio.manager.FileUploadManager; -import alfio.manager.NotificationManager; -import alfio.manager.TicketReservationManager; +import alfio.manager.*; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.support.response.ValidatedResponse; +import alfio.manager.system.ConfigurationManager; import alfio.model.*; import alfio.model.transaction.PaymentProxy; import alfio.model.user.Organization; @@ -40,7 +38,6 @@ import alfio.util.ImageUtil; import alfio.util.LocaleUtil; import alfio.util.TemplateManager; -import lombok.AllArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; @@ -65,7 +62,6 @@ import static alfio.util.EventUtil.firstMatchingCallLink; @RestController -@AllArgsConstructor public class TicketApiV2Controller { private final TicketHelper ticketHelper; @@ -79,6 +75,36 @@ public class TicketApiV2Controller { private final NotificationManager notificationManager; private final BookingInfoTicketLoader bookingInfoTicketLoader; private final TicketRepository ticketRepository; + private final SubscriptionManager subscriptionManager; + private final ConfigurationManager configurationManager; + + public TicketApiV2Controller(TicketHelper ticketHelper, + TicketReservationManager ticketReservationManager, + TicketCategoryRepository ticketCategoryRepository, + MessageSourceManager messageSourceManager, + ExtensionManager extensionManager, + FileUploadManager fileUploadManager, + OrganizationRepository organizationRepository, + TemplateManager templateManager, + NotificationManager notificationManager, + BookingInfoTicketLoader bookingInfoTicketLoader, + TicketRepository ticketRepository, + SubscriptionManager subscriptionManager, + ConfigurationManager configurationManager) { + this.ticketHelper = ticketHelper; + this.ticketReservationManager = ticketReservationManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.messageSourceManager = messageSourceManager; + this.extensionManager = extensionManager; + this.fileUploadManager = fileUploadManager; + this.organizationRepository = organizationRepository; + this.templateManager = templateManager; + this.notificationManager = notificationManager; + this.bookingInfoTicketLoader = bookingInfoTicketLoader; + this.ticketRepository = ticketRepository; + this.subscriptionManager = subscriptionManager; + this.configurationManager = configurationManager; + } @GetMapping(value = { @@ -95,7 +121,7 @@ public void showQrCode(@PathVariable("eventName") String eventName, var event = oData.get().getLeft(); var ticket = oData.get().getRight(); - String qrCodeText = ticket.ticketCode(event.getPrivateKey()); + String qrCodeText = ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()); response.setContentType("image/png"); @@ -121,12 +147,16 @@ public void generateTicketPdf(@PathVariable("eventName") String eventName, try (OutputStream os = response.getOutputStream()) { TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()); Organization organization = organizationRepository.getById(event.getOrganizationId()); - String reservationID = ticketReservationManager.getShortReservationID(event, ticketReservation); + String reservationID = configurationManager.getShortReservationID(event, ticketReservation); var ticketWithMetadata = TicketWithMetadataAttributes.build(ticket, ticketRepository.getTicketMetadata(ticket.getId())); - TemplateProcessor.renderPDFTicket(LocaleUtil.getTicketLanguage(ticket, LocaleUtil.forLanguageTag(ticketReservation.getUserLanguage(), event)), event, ticketReservation, + var locale = LocaleUtil.getTicketLanguage(ticket, LocaleUtil.forLanguageTag(ticketReservation.getUserLanguage(), event)); + TemplateProcessor.renderPDFTicket( + locale, event, ticketReservation, ticketWithMetadata, ticketCategory, organization, templateManager, fileUploadManager, - reservationID, os, ticketHelper.buildRetrieveFieldValuesFunction(), extensionManager); + reservationID, os, ticketHelper.buildRetrieveFieldValuesFunction(), extensionManager, + TemplateProcessor.getSubscriptionDetailsModelForTicket(ticket, subscriptionManager::findDescriptorBySubscriptionId, locale) + ); } catch (IOException ioe) { throw new IllegalStateException(ioe); } @@ -230,7 +260,7 @@ public ResponseEntity<TicketInfo> getTicketInfo(@PathVariable("eventName") Strin ticket.getUuid(), ticketCategory.getName(), ticketReservation.getFullName(), - ticketReservationManager.getShortReservationID(event, ticketReservation), + configurationManager.getShortReservationID(event, ticketReservation), deskPaymentRequired, event.getTimeZone(), DatesWithTimeZoneOffset.fromEvent(event), @@ -284,7 +314,7 @@ public ResponseEntity<OnlineCheckInInfo> getCheckInInfo(@PathVariable("eventName var ticket = info.getTicket(); var event = info.getEventWithCheckInInfo(); var messageSource = messageSourceManager.getMessageSourceFor(event.getOrganizationId(), event.getId()); - String ticketCode = ticket.ticketCode(event.getPrivateKey()); + String ticketCode = ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()); if(MessageDigest.isEqual(DigestUtils.sha256Hex(ticketCode).getBytes(StandardCharsets.UTF_8), checkInCode.getBytes(StandardCharsets.UTF_8))) { var categoryConfiguration = info.getCategoryMetadata().getOnlineConfiguration(); var eventConfiguration = event.getMetadata().getOnlineConfiguration(); diff --git a/src/main/java/alfio/controller/api/v2/user/UserApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/UserApiV2Controller.java index fd3cf6f280..c41239aff8 100644 --- a/src/main/java/alfio/controller/api/v2/user/UserApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/UserApiV2Controller.java @@ -31,7 +31,6 @@ import alfio.manager.user.PublicUserManager; import alfio.model.ContentLanguage; import alfio.util.ErrorsCode; -import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -46,7 +45,6 @@ @RestController @RequestMapping("/api/v2/public/user") -@RequiredArgsConstructor public class UserApiV2Controller { private final PublicUserManager publicUserManager; @@ -55,6 +53,18 @@ public class UserApiV2Controller { private final ExtensionManager extensionManager; private final MessageSourceManager messageSourceManager; + public UserApiV2Controller(PublicUserManager publicUserManager, + TicketReservationManager ticketReservationManager, + ConfigurationManager configurationManager, + ExtensionManager extensionManager, + MessageSourceManager messageSourceManager) { + this.publicUserManager = publicUserManager; + this.ticketReservationManager = ticketReservationManager; + this.configurationManager = configurationManager; + this.extensionManager = extensionManager; + this.messageSourceManager = messageSourceManager; + } + @GetMapping("/me") public ResponseEntity<User> getUserIdentity(Authentication principal) { if(principal != null) { diff --git a/src/main/java/alfio/controller/api/v2/user/support/EventLoader.java b/src/main/java/alfio/controller/api/v2/user/support/EventLoader.java index 5fc2e85b9c..b3736037e5 100644 --- a/src/main/java/alfio/controller/api/v2/user/support/EventLoader.java +++ b/src/main/java/alfio/controller/api/v2/user/support/EventLoader.java @@ -17,7 +17,9 @@ package alfio.controller.api.v2.user.support; import alfio.controller.api.v2.model.AnalyticsConfiguration; +import alfio.controller.api.v2.model.EmbeddingConfiguration; import alfio.controller.api.v2.model.EventWithAdditionalInfo; +import alfio.controller.api.v2.model.OfflinePaymentConfiguration; import alfio.controller.support.Formatters; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.system.ConfigurationManager; @@ -26,7 +28,6 @@ import alfio.model.system.ConfigurationKeys; import alfio.repository.*; import alfio.repository.user.OrganizationRepository; -import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import javax.servlet.http.HttpSession; @@ -35,7 +36,6 @@ import static alfio.model.system.ConfigurationKeys.*; @Component -@AllArgsConstructor public class EventLoader { private final EventRepository eventRepository; @@ -48,6 +48,26 @@ public class EventLoader { private final PromoCodeDiscountRepository promoCodeRepository; private final SubscriptionRepository subscriptionRepository; + public EventLoader(EventRepository eventRepository, + MessageSourceManager messageSourceManager, + EventDescriptionRepository eventDescriptionRepository, + OrganizationRepository organizationRepository, + ConfigurationManager configurationManager, + TicketCategoryRepository ticketCategoryRepository, + TicketRepository ticketRepository, + PromoCodeDiscountRepository promoCodeRepository, + SubscriptionRepository subscriptionRepository) { + this.eventRepository = eventRepository; + this.messageSourceManager = messageSourceManager; + this.eventDescriptionRepository = eventDescriptionRepository; + this.organizationRepository = organizationRepository; + this.configurationManager = configurationManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketRepository = ticketRepository; + this.promoCodeRepository = promoCodeRepository; + this.subscriptionRepository = subscriptionRepository; + } + public Optional<EventWithAdditionalInfo> loadEventInfo(String eventName, HttpSession session) { return eventRepository.findOptionalByShortName(eventName).filter(e -> e.getStatus() != Event.Status.DISABLED)// .map(event -> { @@ -56,7 +76,7 @@ public Optional<EventWithAdditionalInfo> loadEventInfo(String eventName, HttpSes var messageSource = messageSourceAndOverride.getLeft(); var i18nOverride = messageSourceAndOverride.getRight(); - var descriptions = Formatters.applyCommonMark(eventDescriptionRepository.findDescriptionByEventIdAsMap(event.getId())); + var descriptions = Formatters.applyCommonMark(eventDescriptionRepository.findDescriptionByEventIdAsMap(event.getId()), messageSource); var organization = organizationRepository.getContactById(event.getOrganizationId()); @@ -108,11 +128,14 @@ public Optional<EventWithAdditionalInfo> loadEventInfo(String eventName, HttpSes var hasLinkedSubscription = subscriptionRepository.hasLinkedSubscription(event.getId()); + var offlinePaymentConfiguration = new OfflinePaymentConfiguration(configurationsValues.get(SHOW_ONLY_BASIC_INSTRUCTIONS).getValueAsBooleanOrDefault()); + return new EventWithAdditionalInfo(event, locationDescriptor.getMapUrl(), organization, descriptions, bankAccount, bankAccountOwner, formattedDates.beginDate, formattedDates.beginTime, formattedDates.endDate, formattedDates.endTime, - invoicingConf, captchaConf, assignmentConf, promoConf, analyticsConf, + invoicingConf, captchaConf, assignmentConf, promoConf, analyticsConf, offlinePaymentConfiguration, + new EmbeddingConfiguration(configurationsValues.get(EMBED_POST_MESSAGE_ORIGIN).getValueOrNull()), MessageSourceManager.convertPlaceholdersForEachLanguage(i18nOverride), availableTicketsCount, customCss, hasLinkedSubscription); }); } diff --git a/src/main/java/alfio/controller/api/v2/user/support/PurchaseContextInfoBuilder.java b/src/main/java/alfio/controller/api/v2/user/support/PurchaseContextInfoBuilder.java index 39cdfe8bcd..eb9481dbc1 100644 --- a/src/main/java/alfio/controller/api/v2/user/support/PurchaseContextInfoBuilder.java +++ b/src/main/java/alfio/controller/api/v2/user/support/PurchaseContextInfoBuilder.java @@ -31,6 +31,8 @@ public class PurchaseContextInfoBuilder { + private PurchaseContextInfoBuilder() {} + public static Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> configurationsValues(PurchaseContext purchaseContext, ConfigurationManager configurationManager) { return configurationManager.getFor(List.of( MAPS_PROVIDER, @@ -58,6 +60,7 @@ public static Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> co // INVOICE_ADDRESS, VAT_NR, + SHOW_ONLY_BASIC_INSTRUCTIONS, // required by EuVatChecker.reverseChargeEnabled ENABLE_EU_VAT_DIRECTIVE, COUNTRY_OF_BUSINESS, @@ -65,7 +68,8 @@ public static Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> co ENABLE_REVERSE_CHARGE_ONLINE, DISPLAY_TICKETS_LEFT_INDICATOR, - EVENT_CUSTOM_CSS + EVENT_CUSTOM_CSS, + EMBED_POST_MESSAGE_ORIGIN ), purchaseContext.getConfigurationLevel()); } diff --git a/src/main/java/alfio/controller/api/wallet/GoogleWalletApiController.java b/src/main/java/alfio/controller/api/wallet/GoogleWalletApiController.java new file mode 100644 index 0000000000..8b618e1ba6 --- /dev/null +++ b/src/main/java/alfio/controller/api/wallet/GoogleWalletApiController.java @@ -0,0 +1,56 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.wallet; + +import alfio.manager.wallet.GoogleWalletManager; +import alfio.model.EventAndOrganizationId; +import alfio.model.Ticket; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.tuple.Pair; +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 javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Optional; + +@RestController +@RequestMapping("/api/wallet/event/{eventName}/v1") +@Log4j2 +@RequiredArgsConstructor +public class GoogleWalletApiController { + + private final GoogleWalletManager walletManager; + + @GetMapping("/version/passes/{uuid}") + public void walletPass(@PathVariable("eventName") String eventName, + @PathVariable("uuid") String serialNumber, + HttpServletResponse response) throws IOException { + Optional<Pair<EventAndOrganizationId, Ticket>> validationResult = walletManager.validateTicket(eventName, serialNumber); + if (validationResult.isEmpty()) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } else { + String walletUrl = walletManager.createAddToWalletUrl(validationResult.get().getRight(), validationResult.get().getLeft()); + response.sendRedirect(walletUrl); + } + } + +} diff --git a/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java b/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java index 94aa44c0ea..9034d9f709 100644 --- a/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java +++ b/src/main/java/alfio/controller/decorator/SaleableAdditionalService.java @@ -115,17 +115,12 @@ public String getDiscountedPrice() { } public boolean getVatIncluded() { - switch (getVatType()) { - case INHERITED: - return event.isVatIncluded(); - case NONE: - case CUSTOM_EXCLUDED: - return false; - case CUSTOM_INCLUDED: - return true; - default: - return false; - } + return switch (getVatType()) { + case INHERITED -> event.isVatIncluded(); + case NONE, CUSTOM_EXCLUDED -> false; + case CUSTOM_INCLUDED -> true; + default -> false; + }; } public BigDecimal getVatPercentage() { diff --git a/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java b/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java index 7e7629afaf..6d3180ae08 100644 --- a/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java +++ b/src/main/java/alfio/controller/decorator/SaleableTicketCategory.java @@ -22,6 +22,7 @@ import alfio.model.TicketCategory; import alfio.util.MonetaryUtil; import lombok.experimental.Delegate; +import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; import java.time.ZoneId; @@ -111,7 +112,10 @@ public VatStatus getVatStatus() { } public String getFormattedFinalPrice() { - return MonetaryUtil.formatUnit(getFinalPriceToDisplay(getFinalPrice().add(getAppliedDiscount()), getVAT(), getVatStatus()), getCurrencyCode()); + if (StringUtils.isNotEmpty(getCurrencyCode())) { + return MonetaryUtil.formatUnit(getFinalPriceToDisplay(getFinalPrice().add(getAppliedDiscount()), getVAT(), getVatStatus()), getCurrencyCode()); + } + return ""; } public int getMaxTicketsAfterConfiguration() { diff --git a/src/main/java/alfio/controller/form/PaymentForm.java b/src/main/java/alfio/controller/form/PaymentForm.java index acdbaa481d..599e7bea3d 100644 --- a/src/main/java/alfio/controller/form/PaymentForm.java +++ b/src/main/java/alfio/controller/form/PaymentForm.java @@ -46,7 +46,7 @@ public void validate(BindingResult bindingResult, PurchaseContext purchaseContex List<PaymentProxy> allowedProxies = purchaseContext.getAllowedPaymentProxies(); Optional<PaymentProxy> paymentProxyOptional = Optional.ofNullable(paymentProxy); - boolean priceGreaterThanZero = reservationCost.getPriceWithVAT() > 0; + boolean priceGreaterThanZero = reservationCost.priceWithVAT() > 0; boolean multipleProxies = allowedProxies.size() > 1; if (multipleProxies && priceGreaterThanZero && paymentProxyOptional.isEmpty()) { bindingResult.reject(ErrorsCode.STEP_2_MISSING_PAYMENT_METHOD); diff --git a/src/main/java/alfio/controller/form/ReservationCreate.java b/src/main/java/alfio/controller/form/ReservationCreate.java index 5565cab768..b4e3f55c7d 100644 --- a/src/main/java/alfio/controller/form/ReservationCreate.java +++ b/src/main/java/alfio/controller/form/ReservationCreate.java @@ -17,13 +17,13 @@ package alfio.controller.form; import alfio.model.modification.AdditionalServiceReservationModification; -import alfio.model.modification.TicketReservationModification; +import alfio.model.modification.ReservationRequest; import java.util.List; -public interface ReservationCreate { +public interface ReservationCreate<T extends ReservationRequest> { String getPromoCode(); - List<TicketReservationModification> getTickets(); + List<T> getTickets(); List<AdditionalServiceReservationModification> getAdditionalServices(); String getCaptcha(); } diff --git a/src/main/java/alfio/controller/form/ReservationForm.java b/src/main/java/alfio/controller/form/ReservationForm.java index ea165c9c24..b644ea2387 100644 --- a/src/main/java/alfio/controller/form/ReservationForm.java +++ b/src/main/java/alfio/controller/form/ReservationForm.java @@ -25,7 +25,7 @@ //step 1 : choose tickets @Data -public class ReservationForm implements Serializable, ReservationCreate { +public class ReservationForm implements Serializable, ReservationCreate<TicketReservationModification> { private String promoCode; private List<TicketReservationModification> reservation; diff --git a/src/main/java/alfio/controller/form/SearchOptions.java b/src/main/java/alfio/controller/form/SearchOptions.java index 78a39be6e1..747008674f 100644 --- a/src/main/java/alfio/controller/form/SearchOptions.java +++ b/src/main/java/alfio/controller/form/SearchOptions.java @@ -17,16 +17,19 @@ package alfio.controller.form; import lombok.Data; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.UUID; @Data -@Log4j2 public class SearchOptions { + + private static final Logger log = LoggerFactory.getLogger(SearchOptions.class); + private String subscription; private Integer organizer; private String organizerSlug; diff --git a/src/main/java/alfio/controller/form/UpdateProfileForm.java b/src/main/java/alfio/controller/form/UpdateProfileForm.java index 34206aaaa4..da8a52cb12 100644 --- a/src/main/java/alfio/controller/form/UpdateProfileForm.java +++ b/src/main/java/alfio/controller/form/UpdateProfileForm.java @@ -16,18 +16,21 @@ */ package alfio.controller.form; -import lombok.Getter; -import lombok.Setter; - import java.io.Serializable; import java.util.Map; -@Getter -@Setter public class UpdateProfileForm extends ContactAndTicketsForm implements Serializable { private Map<String, String> additionalInfo; public boolean hasAdditionalInfo() { return additionalInfo != null && !additionalInfo.isEmpty(); } + + public Map<String, String> getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(Map<String, String> additionalInfo) { + this.additionalInfo = additionalInfo; + } } diff --git a/src/main/java/alfio/controller/payment/PayPalCallbackController.java b/src/main/java/alfio/controller/payment/PayPalCallbackController.java index 80d1dbdda8..4495e689db 100644 --- a/src/main/java/alfio/controller/payment/PayPalCallbackController.java +++ b/src/main/java/alfio/controller/payment/PayPalCallbackController.java @@ -22,7 +22,6 @@ import alfio.model.PurchaseContext; import alfio.model.TicketReservation; import alfio.model.transaction.token.PayPalToken; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,13 +34,20 @@ @Controller @RequestMapping("/{purchaseContextType}/{purchaseContextIdentifier}/reservation/{reservationId}/payment/paypal") -@RequiredArgsConstructor public class PayPalCallbackController { private final PurchaseContextManager purchaseContextManager; private final TicketReservationManager ticketReservationManager; private final PayPalManager payPalManager; + public PayPalCallbackController(PurchaseContextManager purchaseContextManager, + TicketReservationManager ticketReservationManager, + PayPalManager payPalManager) { + this.purchaseContextManager = purchaseContextManager; + this.ticketReservationManager = ticketReservationManager; + this.payPalManager = payPalManager; + } + @GetMapping("/confirm") public String payPalSuccess(@PathVariable("purchaseContextType") PurchaseContext.PurchaseContextType purchaseContextType, @PathVariable("purchaseContextIdentifier") String purchaseContextId, diff --git a/src/main/java/alfio/controller/payment/SaferpayCallbackController.java b/src/main/java/alfio/controller/payment/SaferpayCallbackController.java index 2583280bf9..0b2a4646d4 100644 --- a/src/main/java/alfio/controller/payment/SaferpayCallbackController.java +++ b/src/main/java/alfio/controller/payment/SaferpayCallbackController.java @@ -20,19 +20,23 @@ import alfio.manager.TicketReservationManager; import alfio.manager.payment.saferpay.PaymentPageInitializeRequestBuilder; import alfio.model.PurchaseContext; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.util.UriComponentsBuilder; @Controller -@RequiredArgsConstructor public class SaferpayCallbackController { private final TicketReservationManager ticketReservationManager; private final PurchaseContextManager purchaseContextManager; + public SaferpayCallbackController(TicketReservationManager ticketReservationManager, + PurchaseContextManager purchaseContextManager) { + this.ticketReservationManager = ticketReservationManager; + this.purchaseContextManager = purchaseContextManager; + } + @GetMapping(PaymentPageInitializeRequestBuilder.CANCEL_URL_TEMPLATE) public String saferpayCancel(@PathVariable("purchaseContextType") PurchaseContext.PurchaseContextType purchaseContextType, @PathVariable("purchaseContextIdentifier") String purchaseContextIdentifier, diff --git a/src/main/java/alfio/controller/payment/api/PaymentApiController.java b/src/main/java/alfio/controller/payment/api/PaymentApiController.java index 5aeb600f3f..66f9f2d661 100644 --- a/src/main/java/alfio/controller/payment/api/PaymentApiController.java +++ b/src/main/java/alfio/controller/payment/api/PaymentApiController.java @@ -24,7 +24,6 @@ import alfio.model.TicketReservation; import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.TransactionInitializationToken; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.ResponseEntity; import org.springframework.util.MultiValueMap; @@ -33,13 +32,20 @@ import java.util.Optional; @RestController -@AllArgsConstructor public class PaymentApiController { private final PaymentManager paymentManager; private final TicketReservationManager ticketReservationManager; private final PurchaseContextManager purchaseContextManager; + public PaymentApiController(PaymentManager paymentManager, + TicketReservationManager ticketReservationManager, + PurchaseContextManager purchaseContextManager) { + this.paymentManager = paymentManager; + this.ticketReservationManager = ticketReservationManager; + this.purchaseContextManager = purchaseContextManager; + } + @PostMapping({"/api/reservation/{reservationId}/payment/{method}/init", "/api/events/{eventName}/reservation/{reservationId}/payment/{method}/init" //<-deprecated }) diff --git a/src/main/java/alfio/controller/payment/api/mollie/MolliePaymentWebhookController.java b/src/main/java/alfio/controller/payment/api/mollie/MolliePaymentWebhookController.java index 4e7f215981..5273cff3c1 100644 --- a/src/main/java/alfio/controller/payment/api/mollie/MolliePaymentWebhookController.java +++ b/src/main/java/alfio/controller/payment/api/mollie/MolliePaymentWebhookController.java @@ -20,9 +20,9 @@ import alfio.manager.TicketReservationManager; import alfio.model.transaction.PaymentContext; import alfio.model.transaction.PaymentProxy; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -36,12 +36,18 @@ import static alfio.manager.payment.MollieWebhookPaymentManager.*; @RestController -@Log4j2 -@AllArgsConstructor public class MolliePaymentWebhookController { + + private static final Logger log = LoggerFactory.getLogger(MolliePaymentWebhookController.class); + private final TicketReservationManager ticketReservationManager; private final PurchaseContextManager purchaseContextManager; + public MolliePaymentWebhookController(TicketReservationManager ticketReservationManager, PurchaseContextManager purchaseContextManager) { + this.ticketReservationManager = ticketReservationManager; + this.purchaseContextManager = purchaseContextManager; + } + @SuppressWarnings("MVCPathVariableInspection") @PostMapping(WEBHOOK_URL_TEMPLATE) public ResponseEntity<String> receivePaymentConfirmation(HttpServletRequest request, diff --git a/src/main/java/alfio/controller/payment/api/saferpay/SaferpayPaymentWebhookController.java b/src/main/java/alfio/controller/payment/api/saferpay/SaferpayPaymentWebhookController.java index 8319ac40cd..950b52c7df 100644 --- a/src/main/java/alfio/controller/payment/api/saferpay/SaferpayPaymentWebhookController.java +++ b/src/main/java/alfio/controller/payment/api/saferpay/SaferpayPaymentWebhookController.java @@ -21,7 +21,6 @@ import alfio.manager.payment.saferpay.PaymentPageInitializeRequestBuilder; import alfio.model.transaction.PaymentContext; import alfio.model.transaction.PaymentProxy; -import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -31,11 +30,16 @@ import java.util.Map; @RestController -@AllArgsConstructor public class SaferpayPaymentWebhookController { + private final TicketReservationManager ticketReservationManager; private final PurchaseContextManager purchaseContextManager; + public SaferpayPaymentWebhookController(TicketReservationManager ticketReservationManager, PurchaseContextManager purchaseContextManager) { + this.ticketReservationManager = ticketReservationManager; + this.purchaseContextManager = purchaseContextManager; + } + @GetMapping(PaymentPageInitializeRequestBuilder.WEBHOOK_URL_TEMPLATE) ResponseEntity<String> handleTransactionNotification(@PathVariable("reservationId") String reservationId) { return purchaseContextManager.findByReservationId(reservationId) diff --git a/src/main/java/alfio/controller/payment/api/stripe/StripePaymentWebhookController.java b/src/main/java/alfio/controller/payment/api/stripe/StripePaymentWebhookController.java index 636ed650b3..fc9389300a 100644 --- a/src/main/java/alfio/controller/payment/api/stripe/StripePaymentWebhookController.java +++ b/src/main/java/alfio/controller/payment/api/stripe/StripePaymentWebhookController.java @@ -19,8 +19,7 @@ import alfio.manager.TicketReservationManager; import alfio.model.transaction.PaymentProxy; import alfio.util.RequestUtils; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -30,27 +29,39 @@ import javax.servlet.http.HttpServletRequest; import java.util.Map; +import static alfio.util.HttpUtils.APPLICATION_JSON_UTF8; + @RestController -@Log4j2 -@AllArgsConstructor public class StripePaymentWebhookController { private final TicketReservationManager ticketReservationManager; + public StripePaymentWebhookController(TicketReservationManager ticketReservationManager) { + this.ticketReservationManager = ticketReservationManager; + } + @PostMapping("/api/payment/webhook/stripe/payment") public ResponseEntity<String> receivePaymentConfirmation(@RequestHeader(value = "Stripe-Signature") String stripeSignature, HttpServletRequest request) { + var httpHeaders = new HttpHeaders(); + httpHeaders.add(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8); return RequestUtils.readRequest(request) .map(content -> { var result = ticketReservationManager.processTransactionWebhook(content, stripeSignature, PaymentProxy.STRIPE, Map.of()); if(result.isSuccessful()) { - return ResponseEntity.ok("OK"); + return ResponseEntity.status(HttpStatus.OK) + .headers(httpHeaders) + .body("OK"); } else if(result.isError()) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result.getReason()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .headers(httpHeaders) + .body(result.getReason()); } - return ResponseEntity.ok(result.getReason()); + return ResponseEntity.status(HttpStatus.OK) + .headers(httpHeaders) + .body(result.getReason()); }) - .orElseGet(() -> ResponseEntity.badRequest().body("NOK")); + .orElseGet(() -> ResponseEntity.badRequest().headers(httpHeaders).body("Malformed request.")); } } diff --git a/src/main/java/alfio/controller/support/CSPConfigurer.java b/src/main/java/alfio/controller/support/CSPConfigurer.java new file mode 100644 index 0000000000..919b75e887 --- /dev/null +++ b/src/main/java/alfio/controller/support/CSPConfigurer.java @@ -0,0 +1,89 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.support; + +import alfio.manager.system.ConfigurationLevel; +import alfio.manager.system.ConfigurationManager; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletResponse; +import java.security.SecureRandom; +import java.util.List; + +import static alfio.model.system.ConfigurationKeys.*; +import static alfio.model.system.ConfigurationKeys.EMBED_ALLOWED_ORIGINS; + +@Component +public class CSPConfigurer { + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private final ConfigurationManager configurationManager; + + public CSPConfigurer(ConfigurationManager configurationManager) { + this.configurationManager = configurationManager; + } + + public String addCspHeader(HttpServletResponse response, boolean embeddingSupported) { + return addCspHeader(response, ConfigurationLevel.system(), embeddingSupported); + } + + public String addCspHeader(HttpServletResponse response, ConfigurationLevel configurationLevel, boolean embeddingSupported) { + + var nonce = getNonce(); + + String reportUri = ""; + + var conf = configurationManager.getFor(List.of(SECURITY_CSP_REPORT_ENABLED, SECURITY_CSP_REPORT_URI, EMBED_ALLOWED_ORIGINS), configurationLevel); + + boolean enabledReport = conf.get(SECURITY_CSP_REPORT_ENABLED).getValueAsBooleanOrDefault(); + if (enabledReport) { + reportUri = " report-uri " + conf.get(SECURITY_CSP_REPORT_URI).getValueOrDefault("/report-csp-violation"); + } + // + // https://csp.withgoogle.com/docs/strict-csp.html + // with base-uri set to 'self' + + var frameAncestors = "'none'"; + var allowedContainer = conf.get(EMBED_ALLOWED_ORIGINS).getValueOrNull(); + if (embeddingSupported && StringUtils.isNotBlank(allowedContainer)) { + var splitHosts = allowedContainer.split("[,\n]"); + frameAncestors = String.join(" ", splitHosts); + // IE11 + response.addHeader("X-Frame-Options", "ALLOW-FROM "+splitHosts[0]); + } else { + response.addHeader("X-Frame-Options", "DENY"); + } + + response.addHeader("Content-Security-Policy", "object-src 'none'; "+ + "script-src 'strict-dynamic' 'nonce-" + nonce + "' 'unsafe-inline' http: https: " + + "'unsafe-hashes' 'sha256-MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc='" // see https://github.com/angular/angular-cli/issues/20864#issuecomment-983672336 + +"; " + + "base-uri 'self'; " + + "frame-ancestors " + frameAncestors + "; " + + reportUri); + + return nonce; + } + + private static String getNonce() { + var nonce = new byte[16]; //128 bit = 16 bytes + SECURE_RANDOM.nextBytes(nonce); + return Hex.encodeHexString(nonce); + } +} diff --git a/src/main/java/alfio/controller/support/FormattedEventDates.java b/src/main/java/alfio/controller/support/FormattedEventDates.java index a60e85ec0d..3c7ba50df9 100644 --- a/src/main/java/alfio/controller/support/FormattedEventDates.java +++ b/src/main/java/alfio/controller/support/FormattedEventDates.java @@ -16,14 +16,21 @@ */ package alfio.controller.support; -import lombok.RequiredArgsConstructor; - import java.util.Map; -@RequiredArgsConstructor public class FormattedEventDates { public final Map<String, String> beginDate; public final Map<String, String> beginTime; public final Map<String, String> endDate; public final Map<String, String> endTime; + + public FormattedEventDates(Map<String, String> beginDate, + Map<String, String> beginTime, + Map<String, String> endDate, + Map<String, String> endTime) { + this.beginDate = beginDate; + this.beginTime = beginTime; + this.endDate = endDate; + this.endTime = endTime; + } } diff --git a/src/main/java/alfio/controller/support/Formatters.java b/src/main/java/alfio/controller/support/Formatters.java index a620236884..dafbd92e1d 100644 --- a/src/main/java/alfio/controller/support/Formatters.java +++ b/src/main/java/alfio/controller/support/Formatters.java @@ -20,21 +20,23 @@ import alfio.model.Event; import alfio.model.LocalizedContent; import alfio.util.MustacheCustomTag; -import lombok.experimental.UtilityClass; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.BiConsumer; -@UtilityClass -@Log4j2 -public class Formatters { +public final class Formatters { + + private Formatters() { + } + + private static final Logger log = LoggerFactory.getLogger(Formatters.class); + + public static final String LINK_NEW_TAB_KEY = "link.new-tab"; public static Map<String, String> getFormattedDate(LocalizedContent localizedContent, ZonedDateTime date, String code, MessageSource messageSource) { if(localizedContent != null && date != null) { @@ -79,13 +81,18 @@ public static FormattedEventDates getFormattedDates(Event e, MessageSource messa } public static Map<String, String> applyCommonMark(Map<String, String> in) { + return applyCommonMark(in, null); + } + + public static Map<String, String> applyCommonMark(Map<String, String> in, MessageSource messageSource) { if (in == null) { return Collections.emptyMap(); } var res = new HashMap<String, String>(); in.forEach((k, v) -> { - res.put(k, MustacheCustomTag.renderToHtmlCommonmarkEscaped(v)); + var targetBlankMessage = messageSource != null ? messageSource.getMessage(LINK_NEW_TAB_KEY, null, Locale.forLanguageTag(k)) : null; + res.put(k, MustacheCustomTag.renderToHtmlCommonmarkEscaped(v, targetBlankMessage)); }); return res; } diff --git a/src/main/java/alfio/controller/support/TemplateProcessor.java b/src/main/java/alfio/controller/support/TemplateProcessor.java index 795584b917..defee0e086 100644 --- a/src/main/java/alfio/controller/support/TemplateProcessor.java +++ b/src/main/java/alfio/controller/support/TemplateProcessor.java @@ -20,6 +20,9 @@ import alfio.manager.FileUploadManager; import alfio.manager.support.PartialTicketTextGenerator; import alfio.model.*; +import alfio.model.metadata.SubscriptionMetadata; +import alfio.model.subscription.Subscription; +import alfio.model.subscription.SubscriptionDescriptor; import alfio.model.user.Organization; import alfio.util.EventUtil; import alfio.util.ImageUtil; @@ -32,21 +35,16 @@ import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver; import com.openhtmltopdf.pdfboxout.PdfBoxRenderer; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; -import lombok.extern.log4j.Log4j2; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.pdmodel.PDDocument; import org.springframework.core.io.ClassPathResource; import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -@Log4j2 public final class TemplateProcessor { private TemplateProcessor() {} @@ -91,16 +89,48 @@ public static void renderPDFTicket(Locale language, String reservationID, OutputStream os, Function<Ticket, List<TicketFieldConfigurationDescriptionAndValue>> retrieveFieldValues, - ExtensionManager extensionManager) throws IOException { + ExtensionManager extensionManager, + Map<String, Object> initialModel) throws IOException { Optional<TemplateResource.ImageData> imageData = extractImageModel(event, fileUploadManager); List<TicketFieldConfigurationDescriptionAndValue> fields = retrieveFieldValues.apply(ticketWithMetadata.getTicket()); - Map<String, Object> model = TemplateResource.buildModelForTicketPDF(organization, event, ticketReservation, ticketCategory, ticketWithMetadata, imageData, reservationID, - fields.stream().collect(Collectors.toMap(TicketFieldConfigurationDescriptionAndValue::getName, TicketFieldConfigurationDescriptionAndValue::getValueDescription))); + var model = new HashMap<>(Objects.requireNonNullElse(initialModel, Map.of())); + model.putAll(TemplateResource.buildModelForTicketPDF(organization, event, ticketReservation, ticketCategory, ticketWithMetadata, imageData, reservationID, + fields.stream().collect(Collectors.toMap(TicketFieldConfigurationDescriptionAndValue::getName, TicketFieldConfigurationDescriptionAndValue::getValueDescription)))); String page = templateManager.renderTemplate(event, TemplateResource.TICKET_PDF, model, language).getTextPart(); renderToPdf(page, os, extensionManager, event); } + public static Map<String, Object> getSubscriptionDetailsModelForTicket(Ticket ticket, + Function<UUID, SubscriptionDescriptor> subscriptionDescriptorLoader, + Locale locale) { + boolean hasSubscription = ticket.getSubscriptionId() != null; + var result = new HashMap<String, Object>(); + result.put("hasSubscription", hasSubscription); + if (hasSubscription) { + var subscriptionDescriptor = subscriptionDescriptorLoader.apply(ticket.getSubscriptionId()); + result.put("subscriptionTitle", subscriptionDescriptor.getLocalizedTitle(locale)); + } + return result; + } + + public static void renderSubscriptionPDF(Subscription subscription, + Locale locale, + SubscriptionDescriptor subscriptionDescriptor, + TicketReservation reservation, + SubscriptionMetadata metadata, + Organization organization, + TemplateManager templateManager, + FileUploadManager fileUploadManager, + String reservationId, + ByteArrayOutputStream os, + ExtensionManager extensionManager) throws IOException { + Optional<TemplateResource.ImageData> imageData = extractImageModel(subscriptionDescriptor, fileUploadManager); + Map<String, Object> model = TemplateResource.buildModelForSubscriptionPDF(subscription, subscriptionDescriptor, organization, metadata, imageData, reservationId, locale, reservation); + String page = templateManager.renderTemplate(subscriptionDescriptor, TemplateResource.SUBSCRIPTION_PDF, model, locale).getTextPart(); + renderToPdf(page, os, extensionManager, subscriptionDescriptor); + } + public static void renderToPdf(String page, OutputStream os, ExtensionManager extensionManager, PurchaseContext purchaseContext) throws IOException { if(extensionManager.handlePdfTransformation(page, purchaseContext, os)) { @@ -113,6 +143,8 @@ public static void renderToPdf(String page, OutputStream os, ExtensionManager ex builder.useProtocolsStreamImplementation(new AlfioInternalFSStreamFactory(), "alfio-internal"); builder.useProtocolsStreamImplementation(new InvalidProtocolFSStreamFactory(), "http", "https", "file", "jar"); builder.useFastMode(); + builder.usePdfUaAccessbility(true); + builder.usePdfAConformance(PdfRendererBuilder.PdfAConformance.PDFA_3_U); var parser = new Parser(); @@ -195,24 +227,23 @@ public static boolean buildReceiptOrInvoicePdf(PurchaseContext purchaseContext, public static String renderReceiptOrInvoicePdfTemplate(PurchaseContext purchaseContext, FileUploadManager fileUploadManager, Locale language, TemplateManager templateManager, Map<String, Object> model, TemplateResource templateResource) { extractImageModel(purchaseContext, fileUploadManager).ifPresent(imageData -> { - model.put("eventImage", imageData.getEventImage()); - model.put("imageWidth", imageData.getImageWidth()); - model.put("imageHeight", imageData.getImageHeight()); + model.put("eventImage", imageData.eventImage()); + model.put("imageWidth", imageData.imageWidth()); + model.put("imageHeight", imageData.imageHeight()); }); return templateManager.renderTemplate(purchaseContext, templateResource, model, language).getTextPart(); } public static Optional<byte[]> buildBillingDocumentPdf(BillingDocument.Type documentType, PurchaseContext purchaseContext, FileUploadManager fileUploadManager, Locale language, TemplateManager templateManager, Map<String, Object> model, ExtensionManager extensionManager) { - switch (documentType) { - case INVOICE: - return buildInvoicePdf(purchaseContext, fileUploadManager, language, templateManager, model, extensionManager); - case RECEIPT: - return buildReceiptPdf(purchaseContext, fileUploadManager, language, templateManager, model, extensionManager); - case CREDIT_NOTE: - return buildCreditNotePdf(purchaseContext, fileUploadManager, language, templateManager, model, extensionManager); - default: - throw new IllegalStateException(documentType + " not supported"); - } + return switch (documentType) { + case INVOICE -> + buildInvoicePdf(purchaseContext, fileUploadManager, language, templateManager, model, extensionManager); + case RECEIPT -> + buildReceiptPdf(purchaseContext, fileUploadManager, language, templateManager, model, extensionManager); + case CREDIT_NOTE -> + buildCreditNotePdf(purchaseContext, fileUploadManager, language, templateManager, model, extensionManager); + default -> throw new IllegalStateException(documentType + " not supported"); + }; } private static Optional<byte[]> buildFrom(PurchaseContext purchaseContext, diff --git a/src/main/java/alfio/controller/support/UserStatus.java b/src/main/java/alfio/controller/support/UserStatus.java new file mode 100644 index 0000000000..78414f10fc --- /dev/null +++ b/src/main/java/alfio/controller/support/UserStatus.java @@ -0,0 +1,26 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.support; + +public record UserStatus(boolean authenticated, + String username, + String alfioVersion, + boolean demoModeEnabled, + boolean devModeEnabled, + boolean prodModeEnabled) { + +} diff --git a/src/main/java/alfio/extension/ConsoleLogger.java b/src/main/java/alfio/extension/ConsoleLogger.java index 396b316438..8b05c1f470 100644 --- a/src/main/java/alfio/extension/ConsoleLogger.java +++ b/src/main/java/alfio/extension/ConsoleLogger.java @@ -16,13 +16,16 @@ */ package alfio.extension; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.stream.Stream; -@Slf4j public class ConsoleLogger { + + private static final Logger log = LoggerFactory.getLogger(ConsoleLogger.class); + private final ExtensionLogger extensionLogger; public ConsoleLogger(ExtensionLogger extensionLogger) { diff --git a/src/main/java/alfio/extension/Extension.java b/src/main/java/alfio/extension/Extension.java index af4a8d13d1..4e25b86815 100644 --- a/src/main/java/alfio/extension/Extension.java +++ b/src/main/java/alfio/extension/Extension.java @@ -18,9 +18,7 @@ package alfio.extension; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -@Getter public class Extension { private final String path; @@ -28,6 +26,22 @@ public class Extension { private final String script; private final boolean enabled; + public String getPath() { + return path; + } + + public String getName() { + return name; + } + + public String getScript() { + return script; + } + + public boolean isEnabled() { + return enabled; + } + public Extension(@JsonProperty("path") String path, @JsonProperty("name") String name, @JsonProperty("script") String script, diff --git a/src/main/java/alfio/extension/ExtensionService.java b/src/main/java/alfio/extension/ExtensionService.java index 0bf8497b75..8b2e4f8d4d 100644 --- a/src/main/java/alfio/extension/ExtensionService.java +++ b/src/main/java/alfio/extension/ExtensionService.java @@ -25,8 +25,6 @@ import alfio.model.user.Organization; import alfio.repository.ExtensionLogRepository; import alfio.repository.ExtensionRepository; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -48,12 +46,9 @@ import static alfio.extension.ScriptingExecutionService.EXTENSION_CONFIGURATION_PARAMETERS; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; @Service -@Log4j2 -@AllArgsConstructor public class ExtensionService { private static final String EVALUATE_RESULT = "res = GSON.fromJson(JSON.stringify(res), returnClass);"; @@ -63,6 +58,7 @@ public class ExtensionService { private static final String OUTPUT = "output"; private static final String EXECUTION_KEY = "executionKey"; private static final String EXTENSION_EVENT = "extensionEvent"; + private final ScriptingExecutionService scriptingExecutionService; private final ExtensionRepository extensionRepository; private final ExtensionLogRepository extensionLogRepository; @@ -70,8 +66,22 @@ public class ExtensionService { private final ExternalConfiguration externalConfiguration; private final NamedParameterJdbcTemplate jdbcTemplate; + public ExtensionService(ScriptingExecutionService scriptingExecutionService, + ExtensionRepository extensionRepository, + ExtensionLogRepository extensionLogRepository, + PlatformTransactionManager platformTransactionManager, + ExternalConfiguration externalConfiguration, + NamedParameterJdbcTemplate jdbcTemplate) { + this.scriptingExecutionService = scriptingExecutionService; + this.extensionRepository = extensionRepository; + this.extensionLogRepository = extensionLogRepository; + this.platformTransactionManager = platformTransactionManager; + this.externalConfiguration = externalConfiguration; + this.jdbcTemplate = jdbcTemplate; + } + + - @AllArgsConstructor private static final class ExtensionLoggerImpl implements ExtensionLogger { private final ExtensionLogRepository extensionLogRepository; @@ -80,6 +90,18 @@ private static final class ExtensionLoggerImpl implements ExtensionLogger { private final String path; private final String name; + private ExtensionLoggerImpl(ExtensionLogRepository extensionLogRepository, + PlatformTransactionManager platformTransactionManager, + String effectivePath, + String path, + String name) { + this.extensionLogRepository = extensionLogRepository; + this.platformTransactionManager = platformTransactionManager; + this.effectivePath = effectivePath; + this.path = path; + this.name = name; + } + @Override public void logWarning(String msg) { executeInNewTransaction(s -> extensionLogRepository.insert(effectivePath, path, name, msg, ExtensionLog.Type.WARNING)); @@ -158,7 +180,7 @@ public void createOrUpdate(String previousPath, String previousName, Extension s for (ExtensionMetadata.Field field : requireNonNullElse(parameters.getFields(), List.<ExtensionMetadata.Field>of())) { for (String level : parameters.getConfigurationLevels()) { int confFieldId = extensionRepository.registerExtensionConfigurationMetadata(extensionId, field.getName(), field.getDescription(), field.getType(), level, field.isRequired()).getKey(); - List<ExtensionParameterKeyValue> filteredParam = extensionParameterKeyValue.stream().filter(kv -> field.getName().equals(kv.getName()) && level.equals(kv.getConfigurationLevel())).collect(toList()); + List<ExtensionParameterKeyValue> filteredParam = extensionParameterKeyValue.stream().filter(kv -> field.getName().equals(kv.getName()) && level.equals(kv.getConfigurationLevel())).toList(); var parameterSources = filteredParam.stream() .map(kv -> new MapSqlParameterSource("ecmId", confFieldId) .addValue("confPath", kv.getConfigurationPath()) @@ -216,8 +238,7 @@ private void deleteAndInsertSetting(String level, String path, List<ExtensionMet extensionRepository.deleteSettingValue(level, path); List<ExtensionMetadataValue> toUpdate2 = (toUpdate == null ? Collections.emptyList() : toUpdate); List<ExtensionMetadataValue> filtered = toUpdate2.stream() - .filter(f -> StringUtils.trimToNull(f.getValue()) != null) - .collect(toList()); + .filter(f -> StringUtils.trimToNull(f.getValue()) != null).toList(); var parameterSources = filtered.stream() .map(kv -> new MapSqlParameterSource("ecmId", kv.getId()) .addValue("confPath", path) diff --git a/src/main/java/alfio/extension/ExtensionUtils.java b/src/main/java/alfio/extension/ExtensionUtils.java index 584c6a523b..2c189d0169 100644 --- a/src/main/java/alfio/extension/ExtensionUtils.java +++ b/src/main/java/alfio/extension/ExtensionUtils.java @@ -18,8 +18,6 @@ import alfio.util.Json; import com.google.gson.*; -import lombok.experimental.UtilityClass; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.codec.digest.HmacUtils; @@ -36,9 +34,10 @@ import java.util.*; import java.util.stream.Collectors; -@Log4j2 -@UtilityClass -public class ExtensionUtils { +public final class ExtensionUtils { + + private ExtensionUtils() { + } private static final Gson JSON_SERIALIZER = Json.GSON.newBuilder() .registerTypeAdapter(Double.class, new DoubleSerializer()) @@ -101,24 +100,22 @@ public static String convertToJson(Object o) { static Object unwrap(Object o) { if (o instanceof Scriptable) { - if (o instanceof NativeArray) { + if (o instanceof NativeArray na) { List<Object> res = new ArrayList<>(); - var na = (NativeArray) o; for (var a : na) { res.add(unwrap(a)); } return res; - } else if (o instanceof NativeJavaObject) { - return ((NativeJavaObject) o).unwrap(); - } else if (o instanceof NativeObject) { - var na = (NativeObject) o; + } else if (o instanceof NativeJavaObject njo) { + return njo.unwrap(); + } else if (o instanceof NativeObject no) { Map<Object, Object> res = new LinkedHashMap<>(); - for (var kv : na.entrySet()) { + for (var kv : no.entrySet()) { res.put(kv.getKey(), unwrap(kv.getValue())); } return res; - } else if (o instanceof IdScriptableObject) { - return parseIdScriptableObject((IdScriptableObject) o); + } else if (o instanceof IdScriptableObject ids) { + return parseIdScriptableObject(ids); } } else if (o instanceof CharSequence) { return o.toString(); @@ -128,17 +125,13 @@ static Object unwrap(Object o) { private static Object parseIdScriptableObject(IdScriptableObject object) { var className = object.getClassName(); - switch (className) { - case "String": - return ScriptRuntime.toCharSequence(object); - case "Boolean": - return Context.jsToJava(object, Boolean.class); - case "Date": { - return Context.jsToJava(object, Date.class); - } - } - // better safe than sorry: we ignore all the unknown objects - return null; + return switch (className) { + case "String" -> ScriptRuntime.toCharSequence(object); + case "Boolean" -> Context.jsToJava(object, Boolean.class); + case "Date" -> Context.jsToJava(object, Date.class); + // better safe than sorry: we ignore all the unknown objects + default -> null; + }; } /** diff --git a/src/main/java/alfio/extension/JSErrorReporter.java b/src/main/java/alfio/extension/JSErrorReporter.java index aae5e6e666..76c01c3c31 100644 --- a/src/main/java/alfio/extension/JSErrorReporter.java +++ b/src/main/java/alfio/extension/JSErrorReporter.java @@ -17,9 +17,10 @@ package alfio.extension; -import lombok.extern.log4j.Log4j2; import org.mozilla.javascript.ErrorReporter; import org.mozilla.javascript.EvaluatorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -28,12 +29,13 @@ * * Implements ErrorReporter interface of Rhino and prints syntax errors in the JS script. */ -@Log4j2 public class JSErrorReporter implements ErrorReporter { + private static final Logger log = LoggerFactory.getLogger(JSErrorReporter.class); + @Override public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) { - log.warn("Warning : " + message); + log.warn("Warning : {}", message); } @Override @@ -43,7 +45,7 @@ public EvaluatorException runtimeError(String message, String sourceName, int li @Override public void error(String message, String sourceName, int line, String lineSource, int lineOffset) { - log.warn("Error : " + message); + log.warn("Error : {}", message); } } diff --git a/src/main/java/alfio/extension/JSNodeVisitor.java b/src/main/java/alfio/extension/JSNodeVisitor.java index 9a1b6d23ef..b45d2e445d 100644 --- a/src/main/java/alfio/extension/JSNodeVisitor.java +++ b/src/main/java/alfio/extension/JSNodeVisitor.java @@ -68,8 +68,8 @@ private void checkNode(AstNode node) { return; } // keep track of function calls - if (node instanceof FunctionCall) { - AstNode target = ((FunctionCall) node).getTarget(); + if (node instanceof FunctionCall functionCall) { + AstNode target = functionCall.getTarget(); if (!(target instanceof PropertyGet)) { Name name = (Name) target; // keep all function calls inside an ArrayList @@ -77,8 +77,8 @@ private void checkNode(AstNode node) { // go back in the script and find the parent i.e. the function in which this node is inside AstNode parentNode = node.getParent(); while (parentNode != null) { - if (parentNode instanceof FunctionNode) { - Name parentName = ((FunctionNode) parentNode).getFunctionName(); + if (parentNode instanceof FunctionNode functionNode) { + Name parentName = functionNode.getFunctionName(); String id = parentName.getIdentifier(); // when the function name is found, check if it was called from somewhere else // if this function is called from another place and it contains another function call, throw an exception @@ -95,16 +95,17 @@ private void checkNode(AstNode node) { || node instanceof DoLoop || node instanceof WithStatement || node instanceof LabeledStatement - || (node instanceof PropertyGet && ((PropertyGet) node).getRight().getString().equals("System")) - || (node instanceof PropertyGet && ((PropertyGet) node).getRight().getString().equals("getClass")) + || (node instanceof PropertyGet pg1 && pg1.getRight().getString().equals("System")) + || (node instanceof PropertyGet pg2 && pg2.getRight().getString().equals("getClass")) || (node instanceof Name && node.getString().equals("newInstance"))) { - throw new ScriptNotValidException("Script not valid. One or more of the following components have been detected: \n" + - "- while() Loop\n" + - "- with() Statement\n" + - "- a labeled statement\n" + - "- Access to java.lang.System\n" + - "- Access to Object.getClass()\n" + - "- Java reflection usage"); + throw new ScriptNotValidException(""" + Script not valid. One or more of the following components have been detected:\s + - while() Loop + - with() Statement + - a labeled statement + - Access to java.lang.System + - Access to Object.getClass() + - Java reflection usage"""); } } diff --git a/src/main/java/alfio/extension/JSON.java b/src/main/java/alfio/extension/JSON.java index e5eafae2c9..16c6f22213 100644 --- a/src/main/java/alfio/extension/JSON.java +++ b/src/main/java/alfio/extension/JSON.java @@ -18,15 +18,18 @@ import alfio.util.Json; import com.google.gson.reflect.TypeToken; -import lombok.experimental.UtilityClass; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.Type; import java.util.Map; -@UtilityClass -@Log4j2 -public class JSON { +public final class JSON { + + private JSON() { + } + + private static final Logger log = LoggerFactory.getLogger(JSON.class); private static final Type PARSE_RETURN_TYPE = new TypeToken<Map<String, Object>>(){}.getType(); diff --git a/src/main/java/alfio/extension/JSSymbol.java b/src/main/java/alfio/extension/JSSymbol.java index 286d73d9ae..1dd0b1807c 100644 --- a/src/main/java/alfio/extension/JSSymbol.java +++ b/src/main/java/alfio/extension/JSSymbol.java @@ -17,18 +17,21 @@ package alfio.extension; +import org.mozilla.javascript.Token; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.FunctionNode; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.VariableInitializer; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.mozilla.javascript.Token; -import org.mozilla.javascript.ast.*; - /** * * @author Ram Kulkarni - * http://ramkulkarni.com/blog/parsing-javascript-code-using-mozilla-rhino/ + * <a href="http://ramkulkarni.com/blog/parsing-javascript-code-using-mozilla-rhino/">(source)</a> * * This class holds reference to AST node and child elements. */ @@ -40,8 +43,7 @@ class JSSymbol { public JSSymbol(AstNode node) { this.node = node; - if (node instanceof FunctionNode) { - FunctionNode funcNode = (FunctionNode)node; + if (node instanceof FunctionNode funcNode) { List<AstNode> args = funcNode.getParams(); if (args != null) { for (AstNode argNode : args) { @@ -59,8 +61,8 @@ public void addChild(JSSymbol child) { if (child.getType() == Token.VAR) { //check if it is already added AstNode childNode = child.getNode(); - if (childNode instanceof VariableInitializer) { - String varName = ((Name)((VariableInitializer) childNode).getTarget()).getIdentifier(); + if (childNode instanceof VariableInitializer variableInitializer) { + String varName = ((Name) variableInitializer.getTarget()).getIdentifier(); if (localVars.containsKey(varName)) { return; } @@ -75,7 +77,7 @@ public void addChild (AstNode node) { addChild(new JSSymbol(node)); } - public ArrayList<JSSymbol> getChildren() { + public List<JSSymbol> getChildren() { return new ArrayList<>(children); } diff --git a/src/main/java/alfio/extension/ScriptingExecutionService.java b/src/main/java/alfio/extension/ScriptingExecutionService.java index 5f291dbf65..5dab61c471 100644 --- a/src/main/java/alfio/extension/ScriptingExecutionService.java +++ b/src/main/java/alfio/extension/ScriptingExecutionService.java @@ -29,8 +29,9 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; -import lombok.extern.log4j.Log4j2; import org.mozilla.javascript.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.net.ConnectException; @@ -54,9 +55,10 @@ // -organizationId-eventId @Service -@Log4j2 public class ScriptingExecutionService { + private static final Logger log = LoggerFactory.getLogger(ScriptingExecutionService.class); + public static final String EXTENSION_NAME = "extensionName"; public static final String EXTENSION_PATH = "path"; public static final String EXTENSION_PARAMS = "params"; @@ -71,8 +73,8 @@ public class ScriptingExecutionService { private final Cache<String, Executor> asyncExecutors = Caffeine.newBuilder() .expireAfterAccess(Duration.ofHours(12)) .removalListener((String key, Executor value, RemovalCause cause) -> { - if (value instanceof ExecutorService) { - ((ExecutorService) value).shutdown(); + if (value instanceof ExecutorService executorService) { + executorService.shutdown(); } }) .build(); @@ -196,8 +198,7 @@ private <T> T executeScriptFinally(String name, String script, Map<String, Objec Object res; res = cx.evaluateString(scope, script, name, 1, null); extensionLogger.logSuccess("Script executed successfully."); - if (res instanceof NativeJavaObject) { - NativeJavaObject nativeRes = (NativeJavaObject) res; + if (res instanceof NativeJavaObject nativeRes) { return (T) nativeRes.unwrap(); } else if(clazz.isInstance(res)) { return (T) res; diff --git a/src/main/java/alfio/extension/SimpleHttpClient.java b/src/main/java/alfio/extension/SimpleHttpClient.java index 3ee7967192..69f44dc0bc 100644 --- a/src/main/java/alfio/extension/SimpleHttpClient.java +++ b/src/main/java/alfio/extension/SimpleHttpClient.java @@ -17,7 +17,8 @@ package alfio.extension; import alfio.util.HttpUtils; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.*; import java.net.URI; @@ -30,9 +31,10 @@ import java.util.Map; import java.util.Set; -@Log4j2 public class SimpleHttpClient { + private static final Logger log = LoggerFactory.getLogger(SimpleHttpClient.class); + private static final Set<String> NULL_REQUEST_BODY = Set.of("GET", "HEAD"); private final HttpClient httpClient; diff --git a/src/main/java/alfio/extension/SimpleHttpClientCachedResponse.java b/src/main/java/alfio/extension/SimpleHttpClientCachedResponse.java index 7cda8c9fe3..75cca9877a 100644 --- a/src/main/java/alfio/extension/SimpleHttpClientCachedResponse.java +++ b/src/main/java/alfio/extension/SimpleHttpClientCachedResponse.java @@ -16,17 +16,38 @@ */ package alfio.extension; -import lombok.AllArgsConstructor; -import lombok.Getter; - import java.util.List; import java.util.Map; -@Getter -@AllArgsConstructor public class SimpleHttpClientCachedResponse { private final boolean successful; private final int code; private final Map<String, List<String>> headers; private final String tempFilePath; + + public boolean isSuccessful() { + return successful; + } + + public int getCode() { + return code; + } + + public Map<String, List<String>> getHeaders() { + return headers; + } + + public String getTempFilePath() { + return tempFilePath; + } + + public SimpleHttpClientCachedResponse(boolean successful, + int code, + Map<String, List<String>> headers, + String tempFilePath) { + this.successful = successful; + this.code = code; + this.headers = headers; + this.tempFilePath = tempFilePath; + } } diff --git a/src/main/java/alfio/extension/SimpleHttpClientResponse.java b/src/main/java/alfio/extension/SimpleHttpClientResponse.java index a4613dae9e..d8e72a20ff 100644 --- a/src/main/java/alfio/extension/SimpleHttpClientResponse.java +++ b/src/main/java/alfio/extension/SimpleHttpClientResponse.java @@ -18,20 +18,26 @@ import alfio.util.Json; import com.google.gson.JsonSyntaxException; -import lombok.AllArgsConstructor; -import lombok.Getter; import java.util.List; import java.util.Map; -@Getter -@AllArgsConstructor public class SimpleHttpClientResponse { private final boolean successful; private final int code; private final Map<String, List<String>> headers; private final String body; + public SimpleHttpClientResponse(boolean successful, + int code, + Map<String, List<String>> headers, + String body) { + this.successful = successful; + this.code = code; + this.headers = headers; + this.body = body; + } + public Object getJsonBody() { return tryParse(body, Object.class); @@ -45,6 +51,7 @@ public <T> T getJsonBody(Class<T> clazz) { return tryParse(body, clazz); } + private static <T> T tryParse(String body, Class<T> clazz) { try { return Json.GSON.fromJson(body, clazz); @@ -52,4 +59,20 @@ private static <T> T tryParse(String body, Class<T> clazz) { return null; } } + + public boolean isSuccessful() { + return successful; + } + + public int getCode() { + return code; + } + + public Map<String, List<String>> getHeaders() { + return headers; + } + + public String getBody() { + return body; + } } diff --git a/src/main/java/alfio/extension/support/SandboxNativeJavaMap.java b/src/main/java/alfio/extension/support/SandboxNativeJavaMap.java index bbfe318056..ed40095c2b 100644 --- a/src/main/java/alfio/extension/support/SandboxNativeJavaMap.java +++ b/src/main/java/alfio/extension/support/SandboxNativeJavaMap.java @@ -37,6 +37,11 @@ public Object get(String name, Scriptable start) { throw new OutOfBoundariesException("Out of boundaries class use."); } + if (map.get(name) == null) { + // prevent NPE on Rhino when map has an explicit null value for a given key + return null; + } + return super.get(name, start); } } diff --git a/src/main/java/alfio/job/Jobs.java b/src/main/java/alfio/job/Jobs.java index de4cbbe490..4f8d581c00 100644 --- a/src/main/java/alfio/job/Jobs.java +++ b/src/main/java/alfio/job/Jobs.java @@ -20,10 +20,10 @@ import alfio.manager.*; import alfio.manager.system.AdminJobExecutor; import alfio.manager.system.AdminJobManager; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Scheduled; @@ -41,10 +41,10 @@ @Component @DependsOn("migrator") @Profile("!" + Initializer.PROFILE_DISABLE_JOBS) -@AllArgsConstructor -@Log4j2 public class Jobs { + private static final Logger log = LoggerFactory.getLogger(Jobs.class); + private static final int ONE_MINUTE = 1000 * 60; private static final int THIRTY_SECONDS = 1000 * 30; @@ -60,6 +60,22 @@ public class Jobs { private final WaitingQueueSubscriptionProcessor waitingQueueSubscriptionProcessor; private final AdminJobManager adminJobManager; + public Jobs(AdminReservationRequestManager adminReservationRequestManager, + FileUploadManager fileUploadManager, + NotificationManager notificationManager, + SpecialPriceTokenGenerator specialPriceTokenGenerator, + TicketReservationManager ticketReservationManager, + WaitingQueueSubscriptionProcessor waitingQueueSubscriptionProcessor, + AdminJobManager adminJobManager) { + this.adminReservationRequestManager = adminReservationRequestManager; + this.fileUploadManager = fileUploadManager; + this.notificationManager = notificationManager; + this.specialPriceTokenGenerator = specialPriceTokenGenerator; + this.ticketReservationManager = ticketReservationManager; + this.waitingQueueSubscriptionProcessor = waitingQueueSubscriptionProcessor; + this.adminJobManager = adminJobManager; + } + //cron each minute: "0 0/1 * * * ?" diff --git a/src/main/java/alfio/job/executor/AssignTicketToSubscriberJobExecutor.java b/src/main/java/alfio/job/executor/AssignTicketToSubscriberJobExecutor.java index 6e41872664..58831e4a9f 100644 --- a/src/main/java/alfio/job/executor/AssignTicketToSubscriberJobExecutor.java +++ b/src/main/java/alfio/job/executor/AssignTicketToSubscriberJobExecutor.java @@ -30,7 +30,8 @@ import alfio.repository.SubscriptionRepository; import alfio.repository.TicketCategoryRepository; import alfio.util.ClockProvider; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.time.LocalDate; @@ -47,11 +48,13 @@ import static alfio.model.system.ConfigurationKeys.GENERATE_TICKETS_FOR_SUBSCRIPTIONS; @Component -@Log4j2 public class AssignTicketToSubscriberJobExecutor implements AdminJobExecutor { + private static final Logger log = LoggerFactory.getLogger(AssignTicketToSubscriberJobExecutor.class); + public static final String EVENT_ID = "eventId"; public static final String ORGANIZATION_ID = "organizationId"; + public static final String FORCE_GENERATION = "forceGeneration"; private final AdminReservationRequestManager requestManager; private final ConfigurationManager configurationManager; private final SubscriptionRepository subscriptionRepository; @@ -90,9 +93,10 @@ public String process(AdminJobSchedule schedule) { // - status = 'ACQUIRED' var subscriptionsByEvent = subscriptionRepository.loadAvailableSubscriptionsByEvent((Integer) metadata.get(EVENT_ID), (Integer) metadata.get(ORGANIZATION_ID)); if (!subscriptionsByEvent.isEmpty()) { + boolean forceGeneration = Boolean.TRUE.equals(metadata.get(FORCE_GENERATION)); eventRepository.findByIds(subscriptionsByEvent.keySet()).forEach(event -> { - // 2. for each event check if the flag is active - boolean generationEnabled = configurationManager.getFor(GENERATE_TICKETS_FOR_SUBSCRIPTIONS, event.getConfigurationLevel()) + // 2. for each event check if the flag is active, unless forceGeneration has been specified + boolean generationEnabled = forceGeneration || configurationManager.getFor(GENERATE_TICKETS_FOR_SUBSCRIPTIONS, event.getConfigurationLevel()) .getValueAsBooleanOrDefault(); if (generationEnabled) { var subscriptions = subscriptionsByEvent.get(event.getId()); diff --git a/src/main/java/alfio/job/executor/BillingDocumentJobExecutor.java b/src/main/java/alfio/job/executor/BillingDocumentJobExecutor.java index 5c075b00c6..267d3441f4 100644 --- a/src/main/java/alfio/job/executor/BillingDocumentJobExecutor.java +++ b/src/main/java/alfio/job/executor/BillingDocumentJobExecutor.java @@ -24,7 +24,6 @@ import alfio.repository.EventRepository; import alfio.repository.user.OrganizationRepository; import alfio.util.RenderedTemplate; -import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import java.util.EnumSet; @@ -35,7 +34,6 @@ import java.util.regex.Pattern; @Component -@AllArgsConstructor public class BillingDocumentJobExecutor implements AdminJobExecutor { private final BillingDocumentManager billingDocumentManager; @@ -44,6 +42,18 @@ public class BillingDocumentJobExecutor implements AdminJobExecutor { private final NotificationManager notificationManager; private final OrganizationRepository organizationRepository; + public BillingDocumentJobExecutor(BillingDocumentManager billingDocumentManager, + TicketReservationManager ticketReservationManager, + EventRepository eventRepository, + NotificationManager notificationManager, + OrganizationRepository organizationRepository) { + this.billingDocumentManager = billingDocumentManager; + this.ticketReservationManager = ticketReservationManager; + this.eventRepository = eventRepository; + this.notificationManager = notificationManager; + this.organizationRepository = organizationRepository; + } + @Override public Set<JobName> getJobNames() { return EnumSet.of(JobName.REGENERATE_INVOICES); diff --git a/src/main/java/alfio/job/executor/ReservationJobExecutor.java b/src/main/java/alfio/job/executor/ReservationJobExecutor.java index 6ac5c48dec..a1c46fa1dc 100644 --- a/src/main/java/alfio/job/executor/ReservationJobExecutor.java +++ b/src/main/java/alfio/job/executor/ReservationJobExecutor.java @@ -19,7 +19,6 @@ import alfio.manager.TicketReservationManager; import alfio.manager.system.AdminJobExecutor; import alfio.model.system.AdminJobSchedule; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import java.util.EnumSet; @@ -28,11 +27,14 @@ import static alfio.manager.system.AdminJobExecutor.JobName.*; @Component -@RequiredArgsConstructor public class ReservationJobExecutor implements AdminJobExecutor { private final TicketReservationManager ticketReservationManager; + public ReservationJobExecutor(TicketReservationManager ticketReservationManager) { + this.ticketReservationManager = ticketReservationManager; + } + @Override public Set<JobName> getJobNames() { return EnumSet.of( diff --git a/src/main/java/alfio/job/executor/RetryFailedExtensionJobExecutor.java b/src/main/java/alfio/job/executor/RetryFailedExtensionJobExecutor.java index a1ab5edaca..132740d71e 100644 --- a/src/main/java/alfio/job/executor/RetryFailedExtensionJobExecutor.java +++ b/src/main/java/alfio/job/executor/RetryFailedExtensionJobExecutor.java @@ -20,14 +20,17 @@ import alfio.extension.ScriptingExecutionService; import alfio.manager.system.AdminJobExecutor; import alfio.model.system.AdminJobSchedule; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.EnumSet; import java.util.Map; import java.util.Set; -@Slf4j public class RetryFailedExtensionJobExecutor implements AdminJobExecutor { + + private static final Logger log = LoggerFactory.getLogger(RetryFailedExtensionJobExecutor.class); + private final ExtensionService extensionService; public RetryFailedExtensionJobExecutor(ExtensionService extensionService) { diff --git a/src/main/java/alfio/job/executor/RetryFailedReservationConfirmationExecutor.java b/src/main/java/alfio/job/executor/RetryFailedReservationConfirmationExecutor.java new file mode 100644 index 0000000000..c2c72f8fbc --- /dev/null +++ b/src/main/java/alfio/job/executor/RetryFailedReservationConfirmationExecutor.java @@ -0,0 +1,51 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.job.executor; + +import alfio.manager.ReservationFinalizer; +import alfio.manager.support.RetryFinalizeReservation; +import alfio.manager.system.AdminJobExecutor; +import alfio.model.system.AdminJobSchedule; +import alfio.util.Json; + +import java.util.EnumSet; +import java.util.Set; + +public class RetryFailedReservationConfirmationExecutor implements AdminJobExecutor { + + private final ReservationFinalizer reservationFinalizer; + private final Json json; + + public RetryFailedReservationConfirmationExecutor(ReservationFinalizer reservationFinalizer, + Json json) { + this.reservationFinalizer = reservationFinalizer; + this.json = json; + } + + @Override + public Set<JobName> getJobNames() { + return EnumSet.of(JobName.RETRY_RESERVATION_CONFIRMATION); + } + + @Override + public String process(AdminJobSchedule schedule) { + var metadata = schedule.getMetadata(); + var retryFinalizeReservation = (String) metadata.get("payload"); + reservationFinalizer.retryFinalizeReservation(json.fromJsonString(retryFinalizeReservation, RetryFinalizeReservation.class)); + return null; + } +} diff --git a/src/main/java/alfio/manager/AdditionalServiceManager.java b/src/main/java/alfio/manager/AdditionalServiceManager.java index b1bd77cfdd..35212f11f6 100644 --- a/src/main/java/alfio/manager/AdditionalServiceManager.java +++ b/src/main/java/alfio/manager/AdditionalServiceManager.java @@ -17,12 +17,12 @@ package alfio.manager; import alfio.model.AdditionalService; +import alfio.model.AdditionalServiceItem; import alfio.model.AdditionalServiceItemExport; import alfio.model.AdditionalServiceText; import alfio.repository.AdditionalServiceItemRepository; import alfio.repository.AdditionalServiceRepository; import alfio.repository.AdditionalServiceTextRepository; -import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +33,6 @@ import java.util.Optional; @Component -@AllArgsConstructor @Transactional public class AdditionalServiceManager { @@ -41,6 +40,14 @@ public class AdditionalServiceManager { private final AdditionalServiceTextRepository additionalServiceTextRepository; private final AdditionalServiceItemRepository additionalServiceItemRepository; + public AdditionalServiceManager(AdditionalServiceRepository additionalServiceRepository, + AdditionalServiceTextRepository additionalServiceTextRepository, + AdditionalServiceItemRepository additionalServiceItemRepository) { + this.additionalServiceRepository = additionalServiceRepository; + this.additionalServiceTextRepository = additionalServiceTextRepository; + this.additionalServiceItemRepository = additionalServiceItemRepository; + } + public List<AdditionalService> loadAllForEvent(int eventId) { return additionalServiceRepository.loadAllForEvent(eventId); @@ -50,7 +57,7 @@ public List<AdditionalServiceText> findAllTextByAdditionalServiceId(int addition return additionalServiceTextRepository.findAllByAdditionalServiceId(additionalServiceId); } - public Map<Integer, Integer> countUsageForEvent(int eventId) { + public Map<Integer, Map<AdditionalServiceItem.AdditionalServiceItemStatus, Integer>> countUsageForEvent(int eventId) { return additionalServiceRepository.getCount(eventId); } diff --git a/src/main/java/alfio/manager/AdminReservationManager.java b/src/main/java/alfio/manager/AdminReservationManager.java index cddbe36d5e..c4445e779b 100644 --- a/src/main/java/alfio/manager/AdminReservationManager.java +++ b/src/main/java/alfio/manager/AdminReservationManager.java @@ -20,6 +20,8 @@ import alfio.manager.i18n.MessageSourceManager; import alfio.manager.payment.PaymentSpecification; import alfio.manager.support.DuplicateReferenceException; +import alfio.manager.support.IncompatibleStateException; +import alfio.manager.support.reservation.ReservationEmailContentHelper; import alfio.manager.system.ReservationPriceCalculator; import alfio.model.*; import alfio.model.PurchaseContext.PurchaseContextType; @@ -42,13 +44,13 @@ import alfio.repository.*; import alfio.repository.user.UserRepository; import alfio.util.*; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -60,6 +62,7 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.validation.MapBindingResult; +import org.springframework.validation.ObjectError; import java.math.BigDecimal; import java.util.*; @@ -79,17 +82,17 @@ import static alfio.util.Wrappers.optionally; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.*; import static org.apache.commons.lang3.StringUtils.firstNonBlank; import static org.apache.commons.lang3.StringUtils.trimToNull; @Component -@Log4j2 -@RequiredArgsConstructor public class AdminReservationManager { - private static final EnumSet<TicketReservationStatus> UPDATE_INVOICE_STATUSES = EnumSet.of(TicketReservationStatus.OFFLINE_PAYMENT, TicketReservationStatus.PENDING); + private static final Logger log = LoggerFactory.getLogger(AdminReservationManager.class); + private static final ErrorCode ERROR_CANNOT_CANCEL_CHECKED_IN_TICKETS = ErrorCode.custom("remove-reservation.failed", "This reservation contains checked-in tickets. Unable to cancel it."); private final PurchaseContextManager purchaseContextManager; private final EventManager eventManager; @@ -117,6 +120,63 @@ public class AdminReservationManager { private final BillingDocumentManager billingDocumentManager; private final ClockProvider clockProvider; private final SubscriptionRepository subscriptionRepository; + private final ReservationEmailContentHelper reservationEmailContentHelper; + + public AdminReservationManager(PurchaseContextManager purchaseContextManager, + EventManager eventManager, + TicketReservationManager ticketReservationManager, + TicketCategoryRepository ticketCategoryRepository, + TicketRepository ticketRepository, + SpecialPriceRepository specialPriceRepository, + TicketReservationRepository ticketReservationRepository, + EventRepository eventRepository, + PlatformTransactionManager transactionManager, + SpecialPriceTokenGenerator specialPriceTokenGenerator, + TicketFieldRepository ticketFieldRepository, + PaymentManager paymentManager, + NotificationManager notificationManager, + MessageSourceManager messageSourceManager, + TemplateManager templateManager, + AdditionalServiceItemRepository additionalServiceItemRepository, + AuditingRepository auditingRepository, + UserRepository userRepository, + ExtensionManager extensionManager, + BillingDocumentRepository billingDocumentRepository, + FileUploadManager fileUploadManager, + PromoCodeDiscountRepository promoCodeDiscountRepository, + AdditionalServiceRepository additionalServiceRepository, + BillingDocumentManager billingDocumentManager, + ClockProvider clockProvider, + SubscriptionRepository subscriptionRepository, + ReservationEmailContentHelper reservationEmailContentHelper) { + this.purchaseContextManager = purchaseContextManager; + this.eventManager = eventManager; + this.ticketReservationManager = ticketReservationManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketRepository = ticketRepository; + this.specialPriceRepository = specialPriceRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.eventRepository = eventRepository; + this.transactionManager = transactionManager; + this.specialPriceTokenGenerator = specialPriceTokenGenerator; + this.ticketFieldRepository = ticketFieldRepository; + this.paymentManager = paymentManager; + this.notificationManager = notificationManager; + this.messageSourceManager = messageSourceManager; + this.templateManager = templateManager; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.auditingRepository = auditingRepository; + this.userRepository = userRepository; + this.extensionManager = extensionManager; + this.billingDocumentRepository = billingDocumentRepository; + this.fileUploadManager = fileUploadManager; + this.promoCodeDiscountRepository = promoCodeDiscountRepository; + this.additionalServiceRepository = additionalServiceRepository; + this.billingDocumentManager = billingDocumentManager; + this.clockProvider = clockProvider; + this.subscriptionRepository = subscriptionRepository; + this.reservationEmailContentHelper = reservationEmailContentHelper; + } //the following methods have an explicit transaction handling, therefore the @Transactional annotation is not helpful here Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> confirmReservation(PurchaseContextType purchaseContextType, @@ -127,25 +187,26 @@ Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> confirmReservat UUID subscriptionId) { DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); TransactionTemplate template = new TransactionTemplate(transactionManager, definition); - return template.execute(status -> { + Result<String> result = template.execute(status -> { try { - Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> result = purchaseContextManager.findBy(purchaseContextType, eventName) + Result<String> confirmationResult = purchaseContextManager.findBy(purchaseContextType, eventName) .map(purchaseContext -> ticketReservationRepository.findOptionalReservationById(reservationId) .filter(r -> r.getStatus() == TicketReservationStatus.PENDING || r.getStatus() == TicketReservationStatus.STUCK) .map(r -> performConfirmation(reservationId, purchaseContext, r, notification, username, subscriptionId)) .orElseGet(() -> Result.error(ErrorCode.ReservationError.UPDATE_FAILED)) ).orElseGet(() -> Result.error(ErrorCode.ReservationError.NOT_FOUND)); - if(!result.isSuccess()) { + if(!confirmationResult.isSuccess()) { log.debug("Reservation confirmation failed for eventName: {} reservationId: {}, username: {}", eventName, reservationId, username); status.setRollbackOnly(); } - return result; + return confirmationResult; } catch (Exception e) { log.error("Error during confirmation of reservation eventName: {} reservationId: {}, username: {}", eventName, reservationId, username); status.setRollbackOnly(); return Result.error(singletonList(ErrorCode.custom("", e.getMessage()))); } }); + return requireNonNull(result).flatMap(this::loadReservation); } public Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> confirmReservation(PurchaseContextType purchaseContextType, String eventName, @@ -189,12 +250,13 @@ public Result<Pair<TicketReservation, List<Ticket>>> createReservation(AdminRese .map(r -> r.flatMap(p -> transactionalCreateReservation(p.getRight(), p.getLeft(), username))) .orElse(Result.error(ErrorCode.EventError.NOT_FOUND)); if (!result.isSuccess()) { - log.debug("Error during update of reservation eventName: {}, username: {}, reservation: {}", eventName, username, AdminReservationModification.summary(input)); + log.warn("Error during update of reservation eventName: {}, username: {}, reservation: {}", eventName, username, AdminReservationModification.summary(input)); status.rollbackToSavepoint(savepoint); } return result; } catch (Exception e) { log.error("Error during update of reservation eventName: {}, username: {}, reservation: {}", eventName, username, AdminReservationModification.summary(input)); + log.debug("Error detail:", e); status.rollbackToSavepoint(savepoint); return Result.error(singletonList(ErrorCode.custom(e instanceof DuplicateReferenceException ? "duplicate-reference" : "", e.getMessage()))); } @@ -244,7 +306,7 @@ private void sendTicketToAttendees(Event event, TicketReservation reservation, P .forEach(t -> { Locale locale = LocaleUtil.forLanguageTag(t.getUserLanguage()); var additionalInfo = ticketReservationManager.retrieveAttendeeAdditionalInfoForTicket(t); - ticketReservationManager.sendTicketByEmail(t, locale, event, ticketReservationManager.getTicketEmailGenerator(event, reservation, locale, additionalInfo)); + reservationEmailContentHelper.sendTicketByEmail(t, locale, event, ticketReservationManager.getTicketEmailGenerator(event, reservation, locale, additionalInfo)); }); } @@ -281,8 +343,8 @@ private Result<Boolean> performUpdate(String reservationId, PurchaseContext purc auditingRepository.insert(reservationId, userRepository.getByUsername(username).getId(), purchaseContext, Audit.EventType.FORCE_VAT_APPLICATION, new Date(), Audit.EntityType.RESERVATION, reservationId, singletonList(singletonMap("vatStatus", newVatStatus))); ticketReservationRepository.addReservationInvoiceOrReceiptModel(reservationId, null); var newPrice = ticketReservationManager.totalReservationCostWithVAT(r.withVatStatus(newVatStatus)).getLeft(); - ticketReservationRepository.resetVat(reservationId, r.isInvoiceRequested(), newVatStatus, r.getSrcPriceCts(), newPrice.getPriceWithVAT(), - newPrice.getVAT(), Math.abs(newPrice.getDiscount()), r.getCurrencyCode()); + ticketReservationRepository.resetVat(reservationId, r.isInvoiceRequested(), newVatStatus, r.getSrcPriceCts(), newPrice.priceWithVAT(), + newPrice.VAT(), Math.abs(newPrice.discount()), r.getCurrencyCode()); } } @@ -330,6 +392,22 @@ public Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> loadRese .orElseGet(() -> Result.error(ErrorCode.ReservationError.NOT_FOUND)); } + @Transactional + public List<Integer> getTicketIdsWithAdditionalData(PurchaseContextType purchaseContextType, String publicIdentifier, String reservationId) { + if(purchaseContextType != PurchaseContextType.event) { + return List.of(); + } + return ticketRepository.findTicketsWithAdditionalData(reservationId, publicIdentifier); + } + + @Transactional + public Optional<Pair<Event, Ticket>> loadFullTicketInfo(String reservationId, String eventShortName, String ticketUUID) { + return purchaseContextManager.findBy(PurchaseContextType.event, eventShortName) + .flatMap(event -> ticketRepository.findOptionalByUUID(ticketUUID) + .filter(ticket -> reservationId.equals(ticket.getTicketsReservationId())) + .map(ticket -> Pair.of((Event) event, ticket))); + } + private Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> loadReservation(String reservationId) { return ticketReservationRepository.findOptionalReservationById(reservationId) @@ -338,12 +416,12 @@ private Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> loadRes .orElseGet(() -> Result.error(ErrorCode.ReservationError.NOT_FOUND)); } - private Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> performConfirmation(String reservationId, - PurchaseContext purchaseContext, - TicketReservation original, - Notification notification, - String username, - UUID subscriptionId) { + private Result<String> performConfirmation(String reservationId, + PurchaseContext purchaseContext, + TicketReservation original, + Notification notification, + String username, + UUID subscriptionId) { try { var reservation = original; @@ -367,6 +445,8 @@ private Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> perform .findFirst() .map(DefaultMessageSourceResolvable::getCode) .orElse("Unknown error"); + log.warn("Unable to apply subscription {}: {}", subscriptionId, + bindingResult.getAllErrors().stream().map(ObjectError::getCode).collect(joining(", "))); return Result.error(ErrorCode.custom(message, String.format("Cannot assign subscription %s to Reservation %s", subscriptionId, reservationId))); } @@ -396,7 +476,7 @@ private Result<Triple<TicketReservation, List<Ticket>, PurchaseContext>> perform notification.isCustomer(), notification.isAttendees(), username); - return loadReservation(reservationId); + return Result.success(reservationId); } catch(Exception e) { return Result.error(ErrorCode.ReservationError.UPDATE_FAILED); } @@ -578,8 +658,8 @@ private void updateExtRefAndLocking(int categoryId, Attendee attendee, Integer t try { ticketRepository.updateExternalReferenceAndLocking(ticketId, categoryId, StringUtils.trimToNull(attendee.getReference()), attendee.isReassignmentForbidden()); } catch (DataIntegrityViolationException ex) { - log.warn("Duplicate found for external reference: "+attendee.getReference()+" and ticketID: " + ticketId); - throw new DuplicateReferenceException("Duplicated Reference: "+attendee.getReference(), ex); + log.warn("Duplicate found for external reference: {} and ticketID: {}", attendee.getReference(), ticketId); + throw new DuplicateReferenceException("Duplicated Reference: " + attendee.getReference(), ex); } } @@ -653,8 +733,8 @@ private void createMissingTickets(Event event, int tickets) { } @Transactional - public void removeTickets(String publicIdentifier, String reservationId, List<Integer> ticketIds, List<Integer> toRefund, boolean notify, boolean issueCreditNote, String username) { - loadReservation(PurchaseContextType.event, publicIdentifier, reservationId, username).ifSuccess(res -> { + public Result<Boolean> removeTickets(String publicIdentifier, String reservationId, List<Integer> ticketIds, List<Integer> toRefund, boolean notify, boolean creditNoteRequested, String username) { + return loadReservation(PurchaseContextType.event, publicIdentifier, reservationId, username).map(res -> { Event e = res.getRight().event().orElseThrow(); TicketReservation reservation = res.getLeft(); List<Ticket> tickets = res.getMiddle(); @@ -663,11 +743,18 @@ public void removeTickets(String publicIdentifier, String reservationId, List<In // ensure that all the tickets ids are present in tickets Assert.isTrue(ticketIdsInReservation.containsAll(ticketIds), "Some ticket ids are not contained in the reservation"); Assert.isTrue(ticketIdsInReservation.containsAll(toRefund), "Some ticket ids to refund are not contained in the reservation"); + if (!ticketsStatusIsCompatibleWithCancellation(tickets.stream().filter(t -> ticketIds.contains(t.getId())))) { + throw new IncompatibleStateException("Cannot remove checked-in tickets"); + } // handleTicketsRefund(toRefund, e, reservation, ticketsById, username); boolean removeReservation = tickets.size() - ticketIds.size() <= 0; + boolean issueCreditNote = (creditNoteRequested && + // if payment method supports refund we require that the user has selected at least one ticket + (!toRefund.isEmpty() || !reservation.getPaymentMethod().isSupportRefund()) + ); removeTicketsFromReservation(reservation, e, ticketIds, notify, username, removeReservation, issueCreditNote); // @@ -677,7 +764,7 @@ public void removeTickets(String publicIdentifier, String reservationId, List<In } else { // recalculate totals var totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); - var currencyCode = totalPrice.getCurrencyCode(); + var currencyCode = totalPrice.currencyCode(); var updatedTickets = ticketRepository.findTicketsInReservation(reservationId); var discount = reservation.getPromoCodeDiscountId() != null ? promoCodeDiscountRepository.findById(reservation.getPromoCodeDiscountId()) : null; List<AdditionalServiceItem> additionalServiceItems = additionalServiceItemRepository.findByReservationUuid(reservationId); @@ -687,6 +774,7 @@ public void removeTickets(String publicIdentifier, String reservationId, List<In unitToCents(calculator.getAppliedDiscount(), currencyCode), calculator.getCurrencyCode(), reservation.getVatNr(), reservation.getVatCountryCode(), reservation.isInvoiceRequested(), reservationId); } + return issueCreditNote; }); } @@ -772,7 +860,14 @@ public Result<Boolean> removeReservation(PurchaseContextType purchaseContextType @Transactional public void creditReservation(PurchaseContextType purchaseContextType, String publicIdentifier, String reservationId, boolean refund, boolean notify, String username) { - loadReservation(purchaseContextType, publicIdentifier, reservationId, username).flatMap(res -> removeReservation(res, refund, notify, username, false, true)); + loadReservation(purchaseContextType, publicIdentifier, reservationId, username) + .ifSuccess(res -> { + if (res.getLeft().getStatus() == TicketReservationStatus.OFFLINE_PAYMENT) { + ticketReservationManager.deleteOfflinePayment(res.getRight().event().orElseThrow(), reservationId, false, true, notify, username); + } else { + removeReservation(res, refund, notify, username, false, true); + } + }); } private Result<Pair<PurchaseContext, TicketReservation>> removeReservation(Triple<TicketReservation, List<Ticket>, PurchaseContext> triple, @@ -816,7 +911,11 @@ private ErrorCode refundIfRequested(TicketReservation reservation, PurchaseConte } private boolean ticketsStatusIsCompatibleWithCancellation(List<Ticket> tickets) { - return tickets.stream().noneMatch(c -> c.getStatus() == Ticket.TicketStatus.CHECKED_IN); + return ticketsStatusIsCompatibleWithCancellation(tickets.stream()); + } + + private boolean ticketsStatusIsCompatibleWithCancellation(Stream<Ticket> ticketsStream) { + return ticketsStream.noneMatch(c -> c.getStatus() == Ticket.TicketStatus.CHECKED_IN); } @Transactional diff --git a/src/main/java/alfio/manager/AdminReservationRequestManager.java b/src/main/java/alfio/manager/AdminReservationRequestManager.java index 865c502ac0..fc04ac6812 100644 --- a/src/main/java/alfio/manager/AdminReservationRequestManager.java +++ b/src/main/java/alfio/manager/AdminReservationRequestManager.java @@ -25,12 +25,12 @@ import alfio.repository.AdminReservationRequestRepository; import alfio.repository.EventRepository; import alfio.repository.user.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.stereotype.Component; @@ -52,10 +52,10 @@ @Component @Transactional -@Log4j2 -@RequiredArgsConstructor public class AdminReservationRequestManager { + private static final Logger log = LoggerFactory.getLogger(AdminReservationRequestManager.class); + private final AdminReservationManager adminReservationManager; private final EventManager eventManager; private final UserRepository userRepository; @@ -63,6 +63,20 @@ public class AdminReservationRequestManager { private final EventRepository eventRepository; private final PlatformTransactionManager transactionManager; + public AdminReservationRequestManager(AdminReservationManager adminReservationManager, + EventManager eventManager, + UserRepository userRepository, + AdminReservationRequestRepository adminReservationRequestRepository, + EventRepository eventRepository, + PlatformTransactionManager transactionManager) { + this.adminReservationManager = adminReservationManager; + this.eventManager = eventManager; + this.userRepository = userRepository; + this.adminReservationRequestRepository = adminReservationRequestRepository; + this.eventRepository = eventRepository; + this.transactionManager = transactionManager; + } + public Result<AdminReservationRequestStats> getRequestStatus(String requestId, String eventName, String username) { return eventManager.getOptionalEventAndOrganizationIdByName(eventName, username) .flatMap(e -> adminReservationRequestRepository.findStatsByRequestIdAndEventId(requestId, e.getId())) @@ -89,8 +103,7 @@ public Result<String> scheduleReservations(String eventName, .entrySet().stream() .filter(e -> e.getValue() > 1) .map(Map.Entry::getKey) - .limit(5) // return max 5 codes - .collect(Collectors.toList()); + .limit(5).toList(); if(!attendeesWithDuplicateReference.isEmpty()) { return Result.error(ErrorCode.custom("DUPLICATE_REFERENCE", "The following codes are duplicate:" + attendeesWithDuplicateReference)); @@ -120,7 +133,7 @@ public Pair<Integer, Integer> processPendingReservations() { try { adminReservationRequestRepository.updateStatus(list); } catch(Exception e) { - log.fatal("cannot update the status of "+list.size()+" reservations", e); + log.warn("cannot update the status of "+list.size()+" reservations", e); } }); @@ -147,6 +160,7 @@ private Result<Triple<TicketReservation, List<Ticket>, Event>> processReservatio .map(triple -> Triple.of(triple.getLeft(), triple.getMiddle(), (Event) triple.getRight())); if(!result.isSuccess()) { status.rollbackToSavepoint(savepoint); + log.warn("Cannot process reservation: \n{}", result.getFormattedErrors()); } return result; } catch(Exception ex) { @@ -158,6 +172,9 @@ private Result<Triple<TicketReservation, List<Ticket>, Event>> processReservatio private MapSqlParameterSource buildParameterSource(Long id, Result<Triple<TicketReservation, List<Ticket>, Event>> result) { boolean success = result.isSuccess(); + if (!success) { + log.warn("Cannot process request {}. Got the following errors:\n{}", id, result.getFormattedErrors()); + } return new MapSqlParameterSource("id", id) .addValue("status", success ? AdminReservationRequest.Status.SUCCESS.name() : AdminReservationRequest.Status.ERROR.name()) .addValue("reservationId", success ? result.getData().getLeft().getId() : null) diff --git a/src/main/java/alfio/manager/AttendeeManager.java b/src/main/java/alfio/manager/AttendeeManager.java index 6cdd14f71a..61fb06554c 100644 --- a/src/main/java/alfio/manager/AttendeeManager.java +++ b/src/main/java/alfio/manager/AttendeeManager.java @@ -29,19 +29,18 @@ import alfio.repository.user.UserRepository; import alfio.util.ClockProvider; import alfio.util.EventUtil; -import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; +import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; @Component -@AllArgsConstructor public class AttendeeManager { + public static final String DEFAULT_OPERATOR_ID = "__DEFAULT__"; private final SponsorScanRepository sponsorScanRepository; private final EventRepository eventRepository; private final TicketRepository ticketRepository; @@ -51,7 +50,30 @@ public class AttendeeManager { private final AdditionalServiceItemRepository additionalServiceItemRepository; private final ClockProvider clockProvider; - public TicketAndCheckInResult registerSponsorScan(String eventShortName, String ticketUid, String notes, SponsorScan.LeadStatus leadStatus, String username) { + public AttendeeManager(SponsorScanRepository sponsorScanRepository, + EventRepository eventRepository, + TicketRepository ticketRepository, + UserRepository userRepository, + UserManager userManager, + TicketFieldRepository ticketFieldRepository, + AdditionalServiceItemRepository additionalServiceItemRepository, + ClockProvider clockProvider) { + this.sponsorScanRepository = sponsorScanRepository; + this.eventRepository = eventRepository; + this.ticketRepository = ticketRepository; + this.userRepository = userRepository; + this.userManager = userManager; + this.ticketFieldRepository = ticketFieldRepository; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.clockProvider = clockProvider; + } + + public TicketAndCheckInResult registerSponsorScan(String eventShortName, + String ticketUid, + String notes, + SponsorScan.LeadStatus leadStatus, + String username, + String operatorId) { int userId = userRepository.getByUsername(username).getId(); Optional<EventAndOrganizationId> maybeEvent = eventRepository.findOptionalEventAndOrganizationIdByShortName(eventShortName); if(maybeEvent.isEmpty()) { @@ -66,12 +88,13 @@ public TicketAndCheckInResult registerSponsorScan(String eventShortName, String if(ticket.getStatus() != Ticket.TicketStatus.CHECKED_IN) { return new TicketAndCheckInResult(new TicketWithCategory(ticket, null), new DefaultCheckInResult(CheckInStatus.INVALID_TICKET_STATE, "not checked-in")); } - Optional<ZonedDateTime> existingRegistration = sponsorScanRepository.getRegistrationTimestamp(userId, event.getId(), ticket.getId()); + var operator = Objects.requireNonNullElse(operatorId, DEFAULT_OPERATOR_ID); + Optional<ZonedDateTime> existingRegistration = sponsorScanRepository.getRegistrationTimestamp(userId, event.getId(), ticket.getId(), operator); if(existingRegistration.isEmpty()) { ZoneId eventZoneId = eventRepository.getZoneIdByEventId(event.getId()); - sponsorScanRepository.insert(userId, ZonedDateTime.now(clockProvider.withZone(eventZoneId)), event.getId(), ticket.getId(), notes, leadStatus); + sponsorScanRepository.insert(userId, ZonedDateTime.now(clockProvider.withZone(eventZoneId)), event.getId(), ticket.getId(), notes, leadStatus, operator); } else { - sponsorScanRepository.updateNotesAndLeadStatus(userId, event.getId(), ticket.getId(), notes, leadStatus); + sponsorScanRepository.updateNotesAndLeadStatus(userId, event.getId(), ticket.getId(), notes, leadStatus, operator); } return new TicketAndCheckInResult(new TicketWithCategory(ticket, null), new DefaultCheckInResult(CheckInStatus.SUCCESS, "success")); } @@ -107,7 +130,9 @@ private List<SponsorAttendeeData> loadAttendeesData(EventAndOrganizationId event .map(scan -> { Ticket ticket = scan.getTicket(); return new SponsorAttendeeData(ticket.getUuid(), scan.getSponsorScan().getTimestamp().format(EventUtil.JSON_DATETIME_FORMATTER), ticket.getFullName(), ticket.getEmail()); - }).collect(Collectors.toList()); + }) + .distinct() + .toList(); } } diff --git a/src/main/java/alfio/manager/BillingDocumentManager.java b/src/main/java/alfio/manager/BillingDocumentManager.java index b3ed54a0e4..45b9b705ca 100644 --- a/src/main/java/alfio/manager/BillingDocumentManager.java +++ b/src/main/java/alfio/manager/BillingDocumentManager.java @@ -30,10 +30,10 @@ import alfio.util.Json; import alfio.util.TemplateResource; import ch.digitalfondue.npjt.AffectedRowCountAndKey; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -48,18 +48,20 @@ import static alfio.model.TicketReservation.TicketReservationStatus.CANCELLED; import static alfio.model.TicketReservation.TicketReservationStatus.PENDING; import static alfio.model.system.ConfigurationKeys.*; +import static alfio.util.ReservationUtil.collectTicketsWithCategory; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; import static org.apache.commons.lang3.StringUtils.trimToNull; +import static org.springframework.http.MediaType.APPLICATION_PDF_VALUE; @Component -@AllArgsConstructor -@Log4j2 public class BillingDocumentManager { + + private static final Logger log = LoggerFactory.getLogger(BillingDocumentManager.class); + static final String CREDIT_NOTE_NUMBER = "creditNoteNumber"; - private static final String APPLICATION_PDF = "application/pdf"; private final BillingDocumentRepository billingDocumentRepository; private final Json json; private final ConfigurationManager configurationManager; @@ -73,6 +75,32 @@ public class BillingDocumentManager { private final ExtensionManager extensionManager; private final InvoiceSequencesRepository invoiceSequencesRepository; + public BillingDocumentManager(BillingDocumentRepository billingDocumentRepository, + Json json, + ConfigurationManager configurationManager, + TicketRepository ticketRepository, + TicketCategoryRepository ticketCategoryRepository, + OrganizationRepository organizationRepository, + UserRepository userRepository, + AuditingRepository auditingRepository, + TicketReservationRepository ticketReservationRepository, + ClockProvider clockProvider, + ExtensionManager extensionManager, + InvoiceSequencesRepository invoiceSequencesRepository) { + this.billingDocumentRepository = billingDocumentRepository; + this.json = json; + this.configurationManager = configurationManager; + this.ticketRepository = ticketRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.organizationRepository = organizationRepository; + this.userRepository = userRepository; + this.auditingRepository = auditingRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.clockProvider = clockProvider; + this.extensionManager = extensionManager; + this.invoiceSequencesRepository = invoiceSequencesRepository; + } + public Optional<ZonedDateTime> findFirstInvoiceDate(int eventId) { return billingDocumentRepository.findFirstInvoiceGenerationDate(eventId); @@ -86,7 +114,7 @@ static boolean mustGenerateBillingDocument(OrderSummary summary, TicketReservati return !summary.getFree() && (!summary.getNotYetPaid() || (summary.getWaitingForPayment() && ticketReservation.isInvoiceRequested())); } - List<Mailer.Attachment> generateBillingDocumentAttachment(PurchaseContext purchaseContext, + public List<Mailer.Attachment> generateBillingDocumentAttachment(PurchaseContext purchaseContext, TicketReservation ticketReservation, Locale language, BillingDocument.Type documentType, @@ -96,17 +124,15 @@ List<Mailer.Attachment> generateBillingDocumentAttachment(PurchaseContext purcha model.put("reservationId", ticketReservation.getId()); model.put("eventId", purchaseContext.event().map(ev -> Integer.toString(ev.getId())).orElse(null)); model.put("language", json.asJsonString(language)); - model.put("reservationEmailModel", json.asJsonString(getOrCreateBillingDocument(purchaseContext, ticketReservation, username, orderSummary).getModel())); - switch (documentType) { - case INVOICE: - return Collections.singletonList(new Mailer.Attachment("invoice.pdf", null, APPLICATION_PDF, model, Mailer.AttachmentIdentifier.INVOICE_PDF)); - case RECEIPT: - return Collections.singletonList(new Mailer.Attachment("receipt.pdf", null, APPLICATION_PDF, model, Mailer.AttachmentIdentifier.RECEIPT_PDF)); - case CREDIT_NOTE: - return Collections.singletonList(new Mailer.Attachment("credit-note.pdf", null, APPLICATION_PDF, model, Mailer.AttachmentIdentifier.CREDIT_NOTE_PDF)); - default: - throw new IllegalStateException(documentType+" is not supported"); - } + model.put("reservationEmailModel", json.asJsonString(internalGetOrCreate(purchaseContext, ticketReservation, username, orderSummary).getModel())); + return switch (documentType) { + case INVOICE -> + Collections.singletonList(new Mailer.Attachment("invoice.pdf", null, APPLICATION_PDF_VALUE, model, Mailer.AttachmentIdentifier.INVOICE_PDF)); + case RECEIPT -> + Collections.singletonList(new Mailer.Attachment("receipt.pdf", null, APPLICATION_PDF_VALUE, model, Mailer.AttachmentIdentifier.RECEIPT_PDF)); + case CREDIT_NOTE -> + Collections.singletonList(new Mailer.Attachment("credit-note.pdf", null, APPLICATION_PDF_VALUE, model, Mailer.AttachmentIdentifier.CREDIT_NOTE_PDF)); + }; } @Transactional @@ -144,6 +170,10 @@ BillingDocument createBillingDocument(PurchaseContext purchaseContext, TicketRes @Transactional public BillingDocument getOrCreateBillingDocument(PurchaseContext purchaseContext, TicketReservation reservation, String username, OrderSummary orderSummary) { + return internalGetOrCreate(purchaseContext, reservation, username, orderSummary); + } + + private BillingDocument internalGetOrCreate(PurchaseContext purchaseContext, TicketReservation reservation, String username, OrderSummary orderSummary) { Optional<BillingDocument> existing = billingDocumentRepository.findLatestByReservationId(reservation.getId()); return existing.orElseGet(() -> createBillingDocument(purchaseContext, reservation, username, orderSummary)); } @@ -220,15 +250,7 @@ private Map<String, Object> prepareModelForBillingDocument(PurchaseContext purch Map<Integer, List<Ticket>> ticketsByCategory = ticketRepository.findTicketsInReservation(reservation.getId()) .stream() .collect(groupingBy(Ticket::getCategoryId)); - final List<TicketWithCategory> ticketsWithCategory; - if(!ticketsByCategory.isEmpty()) { - ticketsWithCategory = ticketCategoryRepository.findByIds(ticketsByCategory.keySet()) - .stream() - .flatMap(tc -> ticketsByCategory.get(tc.getId()).stream().map(t -> new TicketWithCategory(t, tc))) - .collect(toList()); - } else { - ticketsWithCategory = Collections.emptyList(); - } + List<TicketWithCategory> ticketsWithCategory = collectTicketsWithCategory(ticketsByCategory, ticketCategoryRepository); var reservationShortId = configurationManager.getShortReservationID(purchaseContext, reservation); Map<String, Object> model = TemplateResource.prepareModelForConfirmationEmail(organization, purchaseContext, reservation, vat, ticketsWithCategory, summary, "", "", reservationShortId, invoiceAddress, bankAccountNr, bankAccountOwner, Map.of()); boolean euBusiness = StringUtils.isNotBlank(reservation.getVatCountryCode()) && StringUtils.isNotBlank(reservation.getVatNr()) @@ -239,6 +261,7 @@ private Map<String, Object> prepareModelForBillingDocument(PurchaseContext purch model.put("publicId", configurationManager.getPublicReservationID(purchaseContext, reservation)); var additionalInfo = ticketReservationRepository.getAdditionalInfo(reservation.getId()); model.put("invoicingAdditionalInfo", additionalInfo.getInvoicingAdditionalInfo()); + model.put("proforma", !additionalInfo.getInvoicingAdditionalInfo().isEmpty()); model.put("billingDetails", additionalInfo.getBillingDetails()); if(type == CREDIT_NOTE) { model.put(CREDIT_NOTE_NUMBER, creditNoteNumber); diff --git a/src/main/java/alfio/manager/CheckInManager.java b/src/main/java/alfio/manager/CheckInManager.java index 7e75579f48..cb1f1f1c23 100644 --- a/src/main/java/alfio/manager/CheckInManager.java +++ b/src/main/java/alfio/manager/CheckInManager.java @@ -20,7 +20,10 @@ import alfio.manager.system.ConfigurationManager; import alfio.model.*; import alfio.model.Ticket.TicketStatus; +import alfio.model.api.v1.admin.CheckInLogEntry; import alfio.model.audit.ScanAudit; +import alfio.model.checkin.AttendeeSearchResults; +import alfio.model.decorator.TicketPriceContainer; import alfio.model.support.CheckInOutputColorConfiguration; import alfio.model.transaction.PaymentProxy; import alfio.repository.*; @@ -29,21 +32,20 @@ import alfio.repository.user.UserRepository; import alfio.util.*; import com.google.gson.reflect.TypeToken; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; @@ -66,11 +68,12 @@ @Component @Transactional -@Log4j2 -@AllArgsConstructor public class CheckInManager { + private static final Logger log = LoggerFactory.getLogger(CheckInManager.class); + static final Pattern CYPHER_SPLITTER = Pattern.compile("\\|"); + private static final int SEARCH_ATTENDEES_LIMIT = 20; private final TicketRepository ticketRepository; private final EventRepository eventRepository; private final TicketReservationRepository ticketReservationRepository; @@ -87,6 +90,38 @@ public class CheckInManager { private final PollRepository pollRepository; private final ClockProvider clockProvider; + public CheckInManager(TicketRepository ticketRepository, + EventRepository eventRepository, + TicketReservationRepository ticketReservationRepository, + TicketFieldRepository ticketFieldRepository, + TicketCategoryRepository ticketCategoryRepository, + ScanAuditRepository scanAuditRepository, + AuditingRepository auditingRepository, + ConfigurationManager configurationManager, + OrganizationRepository organizationRepository, + UserRepository userRepository, + TicketReservationManager ticketReservationManager, + ExtensionManager extensionManager, + AdditionalServiceItemRepository additionalServiceItemRepository, + PollRepository pollRepository, + ClockProvider clockProvider) { + this.ticketRepository = ticketRepository; + this.eventRepository = eventRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.ticketFieldRepository = ticketFieldRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.scanAuditRepository = scanAuditRepository; + this.auditingRepository = auditingRepository; + this.configurationManager = configurationManager; + this.organizationRepository = organizationRepository; + this.userRepository = userRepository; + this.ticketReservationManager = ticketReservationManager; + this.extensionManager = extensionManager; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.pollRepository = pollRepository; + this.clockProvider = clockProvider; + } + private void checkIn(String uuid) { Ticket ticket = ticketRepository.findByUUID(uuid); @@ -100,7 +135,31 @@ private void acquire(String uuid) { Ticket ticket = ticketRepository.findByUUID(uuid); Validate.isTrue(ticket.getStatus() == TicketStatus.TO_BE_PAID); ticketRepository.updateTicketStatusWithUUID(uuid, TicketStatus.ACQUIRED.toString()); - ticketReservationManager.registerAlfioTransaction(eventRepository.findById(ticket.getEventId()), ticket.getTicketsReservationId(), PaymentProxy.ON_SITE); + ticketReservationManager.registerAlfioTransactionForOnsitePayment(eventRepository.findById(ticket.getEventId()), ticket.getTicketsReservationId()); + } + + public AttendeeSearchResults searchAttendees(Event event, String query, int page) { + if (StringUtils.isBlank(query)) { + return new AttendeeSearchResults(0, 0, 0, 0, List.of()); + } + int eventId = event.getId(); + var search = "%" + query + "%"; + var results = ticketRepository.searchAttendees(eventId, search, SEARCH_ATTENDEES_LIMIT, SEARCH_ATTENDEES_LIMIT * page); + var statistics = ticketRepository.countSearchResults(eventId, search); + var attendees = results.stream().map(fi -> { + var ticket = fi.getTicket(); + var reservation = fi.getTicketReservation(); + String amountToPay = null; + if (reservation.getPaymentMethod() == PaymentProxy.ON_SITE) { + var priceContainer = TicketPriceContainer.from(ticket, reservation.getVatStatus(), reservation.getVAT(), event.getVatStatus(), reservation.getDiscount().orElse(null)); + amountToPay = event.getCurrency() + " " + MonetaryUtil.formatUnit(priceContainer.getFinalPrice(), event.getCurrency()); + } + return new AttendeeSearchResults.Attendee(ticket.getUuid(), ticket.getFirstName(), + ticket.getLastName(), fi.getTicketCategory().getName(), fi.getTicketAdditionalInfo(), + ticket.getStatus(), amountToPay); + }).collect(Collectors.toList()); + int totalPages = (int) Math.ceil((statistics.getTotal() / (double) SEARCH_ATTENDEES_LIMIT)); + return new AttendeeSearchResults(statistics.getTotal(), statistics.getCheckedIn(), totalPages, page, attendees); } /** @@ -276,7 +335,7 @@ private TicketAndCheckInResult extractStatus(Optional<? extends EventCheckInInfo tc.getName(), from, to, formattedNow))); } - if (!code.equals(ticket.ticketCode(event.getPrivateKey()))) { + if (!code.equals(ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()))) { return new TicketAndCheckInResult(null, new DefaultCheckInResult(INVALID_TICKET_CODE, "Ticket qr code does not match")); } @@ -369,18 +428,18 @@ public Predicate<EventAndOrganizationId> isOfflineCheckInAndLabelPrintingEnabled public Map<String,String> getEncryptedAttendeesInformation(Event ev, Set<String> additionalFields, List<Integer> ids) { - return Optional.ofNullable(ev).filter(isOfflineCheckInEnabled()).map(event -> { + boolean caseInsensitiveQRCode = ev.supportsQRCodeCaseInsensitive(); Map<Integer, TicketCategory> categories = ticketCategoryRepository.findByEventIdAsMap(event.getId()); String eventKey = event.getPrivateKey(); - Function<FullTicketInfo, String> hashedHMAC = ticket -> DigestUtils.sha256Hex(ticket.hmacTicketInfo(eventKey)); + Function<FullTicketInfo, String> hashedHMAC = ticket -> DigestUtils.sha256Hex(ticket.hmacTicketInfo(eventKey, caseInsensitiveQRCode)); var outputColorConfiguration = getOutputColorConfiguration(event, configurationManager); // fetch polls for event, in order to determine if we have to print PIN or not var polls = pollRepository.findAllForEvent(event.getId()); boolean hasPolls = !polls.isEmpty(); - var allowedTags = hasPolls ? polls.stream().flatMap(p -> p.getAllowedTags().stream()).collect(Collectors.toList()) : List.<String>of(); + var allowedTags = hasPolls ? polls.stream().flatMap(p -> p.allowedTags().stream()).collect(Collectors.toList()) : List.<String>of(); Function<FullTicketInfo, String> encryptedBody = ticket -> { Map<String, String> info = new HashMap<>(); @@ -401,6 +460,7 @@ public Map<String,String> getEncryptedAttendeesInformation(Event ev, Set<String> if (!additionalFields.isEmpty()) { Map<String, String> fields = new HashMap<>(); fields.put("company", trimToEmpty(ticket.getBillingDetails().getCompanyName())); + fields.put("category", ticket.getTicketCategory().getName()); fields.putAll(ticketFieldRepository.findValueForTicketId(ticket.getId(), additionalFields).stream() .map(vd -> { try { @@ -443,7 +503,7 @@ public Map<String,String> getEncryptedAttendeesInformation(Event ev, Set<String> if(!additionalServicesInfo.isEmpty()) { info.put("additionalServicesInfoJson", Json.toJson(additionalServicesInfo)); } - String key = ticket.ticketCode(eventKey); + String key = ticket.ticketCode(eventKey, caseInsensitiveQRCode); return encrypt(key, Json.toJson(info)); }; return ticketRepository.findAllFullTicketInfoAssignedByEventId(event.getId(), ids) @@ -487,12 +547,12 @@ List<AdditionalServiceInfo> getAdditionalServicesForTicket(TicketInfoContainer t List<BookedAdditionalService> additionalServices = additionalServiceItemRepository.getAdditionalServicesBookedForReservation(ticketsReservationId, ticket.getUserLanguage(), ticket.getEventId()); boolean additionalServicesEmpty = additionalServices.isEmpty(); if(!additionalServicesEmpty) { - List<Integer> additionalServiceIds = additionalServices.stream().map(BookedAdditionalService::getAdditionalServiceId).collect(Collectors.toList()); + List<Integer> additionalServiceIds = additionalServices.stream().map(BookedAdditionalService::additionalServiceId).collect(Collectors.toList()); Map<Integer, List<TicketFieldValueForAdditionalService>> fields = ticketFieldRepository.loadTicketFieldsForAdditionalService(ticket.getId(), additionalServiceIds) .stream().collect(Collectors.groupingBy(TicketFieldValueForAdditionalService::getAdditionalServiceId)); return additionalServices.stream() - .map(as -> new AdditionalServiceInfo(as.getAdditionalServiceName(), as.getCount(), fields.get(as.getAdditionalServiceId()))) + .map(as -> new AdditionalServiceInfo(as.additionalServiceName(), as.count(), fields.get(as.additionalServiceId()))) .collect(Collectors.toList()); } return List.of(); @@ -506,6 +566,13 @@ public CheckInStatistics getStatistics(String eventName, String username) { .orElse(null); } + public List<CheckInLogEntry> retrieveLogEntries(String eventName, String username) { + return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) + .filter(EventManager.checkOwnership(username, organizationRepository)) + .map(event -> scanAuditRepository.loadEntries(event.getId())) + .orElse(List.of()); + } + private boolean areStatsEnabled(EventAndOrganizationId event) { return configurationManager.getFor(CHECK_IN_STATS, event.getConfigurationLevel()).getValueAsBooleanOrDefault(); } diff --git a/src/main/java/alfio/manager/DemoModeDataManager.java b/src/main/java/alfio/manager/DemoModeDataManager.java index 65c1c769ed..d11ab52971 100644 --- a/src/main/java/alfio/manager/DemoModeDataManager.java +++ b/src/main/java/alfio/manager/DemoModeDataManager.java @@ -22,31 +22,49 @@ import alfio.model.user.User; import alfio.repository.EventDeleterRepository; import alfio.repository.EventRepository; +import alfio.repository.OrganizationDeleterRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; import alfio.repository.user.join.UserOrganizationRepository; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.time.DateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.annotation.Transactional; import java.util.Date; +import java.util.EnumSet; import java.util.List; +import java.util.Set; -@AllArgsConstructor -@Log4j2 public class DemoModeDataManager { + + private static final Logger log = LoggerFactory.getLogger(DemoModeDataManager.class); + private final UserRepository userRepository; private final UserOrganizationRepository userOrganizationRepository; - private final OrganizationRepository organizationRepository; private final EventDeleterRepository eventDeleterRepository; private final EventRepository eventRepository; private final ConfigurationManager configurationManager; + private final OrganizationDeleterRepository organizationDeleterRepository; + + public DemoModeDataManager(UserRepository userRepository, + UserOrganizationRepository userOrganizationRepository, + EventDeleterRepository eventDeleterRepository, + EventRepository eventRepository, + ConfigurationManager configurationManager, + OrganizationDeleterRepository organizationDeleterRepository) { + this.userRepository = userRepository; + this.userOrganizationRepository = userOrganizationRepository; + this.eventDeleterRepository = eventDeleterRepository; + this.eventRepository = eventRepository; + this.configurationManager = configurationManager; + this.organizationDeleterRepository = organizationDeleterRepository; + } public List<Integer> findExpiredUsers(Date date) { - return userRepository.findUsersToDeleteOlderThan(date, User.Type.DEMO); + return userRepository.findUsersToDeleteOlderThan(date, Set.of(User.Type.DEMO.name(), User.Type.API_KEY.name())); } public void deleteAccounts(List<Integer> userIds) { @@ -57,8 +75,7 @@ public void deleteAccounts(List<Integer> userIds) { log.info("found {} events to delete", disabledEventIds.size()); disabledEventIds.forEach(eventDeleterRepository::deleteAllForEvent); userIds.forEach(userRepository::deleteUserAndReferences); - int deletedOrganizations = organizationRepository.deleteOrganizationsIfEmpty(organizationIds); - log.info("deleted {} empty organizations", deletedOrganizations); + organizationDeleterRepository.deleteEmptyOrganizations(organizationIds); } } diff --git a/src/main/java/alfio/manager/EuVatChecker.java b/src/main/java/alfio/manager/EuVatChecker.java index ec46b1ce8d..531d12163b 100644 --- a/src/main/java/alfio/manager/EuVatChecker.java +++ b/src/main/java/alfio/manager/EuVatChecker.java @@ -17,7 +17,10 @@ package alfio.manager; import alfio.manager.system.ConfigurationManager; -import alfio.model.*; +import alfio.model.Audit; +import alfio.model.Configurable; +import alfio.model.PurchaseContext; +import alfio.model.VatDetail; import alfio.model.system.ConfigurationKeys; import alfio.repository.AuditingRepository; import alfio.util.ItalianTaxIdValidator; @@ -25,8 +28,6 @@ import ch.digitalfondue.vatchecker.EUVatChecker; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; @@ -36,15 +37,12 @@ import java.util.*; import java.util.function.BiFunction; import java.util.function.BooleanSupplier; -import java.util.function.Supplier; import static alfio.model.Audit.EntityType.RESERVATION; import static alfio.model.Audit.EventType.*; import static alfio.model.system.ConfigurationKeys.*; @Component -@Log4j2 -@AllArgsConstructor public class EuVatChecker { private final ConfigurationManager configurationManager; @@ -56,6 +54,12 @@ public class EuVatChecker { .expireAfterWrite(Duration.ofMinutes(15)) .build(); + public EuVatChecker(ConfigurationManager configurationManager, AuditingRepository auditingRepository, ExtensionManager extensionManager) { + this.configurationManager = configurationManager; + this.auditingRepository = auditingRepository; + this.extensionManager = extensionManager; + } + public boolean isReverseChargeEnabledFor(PurchaseContext configurable) { return reverseChargeEnabled(configurationManager, configurable); } @@ -111,19 +115,11 @@ public void logSuccessfulValidation(VatDetail detail, String reservationId, Purc List<Map<String, Object>> modifications = List.of( Map.of("vatNumber", detail.getVatNr(), "country", detail.getCountry(), "validationType", detail.getType()) ); - Audit.EventType eventType = null; - switch(detail.getType()) { - case VIES: - case EXTRA_EU: - eventType = VAT_VALIDATION_SUCCESSFUL; - break; - case SKIPPED: - eventType = VAT_VALIDATION_SKIPPED; - break; - case FORMAL: - eventType = VAT_FORMAL_VALIDATION_SUCCESSFUL; - break; - } + Audit.EventType eventType = switch (detail.getType()) { + case VIES, EXTRA_EU -> VAT_VALIDATION_SUCCESSFUL; + case SKIPPED -> VAT_VALIDATION_SKIPPED; + case FORMAL -> VAT_FORMAL_VALIDATION_SUCCESSFUL; + }; auditingRepository.insert(reservationId, null, purchaseContext, eventType, new Date(), RESERVATION, reservationId, modifications); } diff --git a/src/main/java/alfio/manager/EventManager.java b/src/main/java/alfio/manager/EventManager.java index 41a46dca2b..1239e11582 100644 --- a/src/main/java/alfio/manager/EventManager.java +++ b/src/main/java/alfio/manager/EventManager.java @@ -47,8 +47,6 @@ import alfio.util.MonetaryUtil; import alfio.util.RequestUtils; import ch.digitalfondue.npjt.AffectedRowCountAndKey; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.lang3.ObjectUtils; @@ -56,6 +54,8 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Triple; import org.flywaydb.core.Flyway; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -88,14 +88,14 @@ import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNullElse; import static java.util.Objects.requireNonNullElseGet; -import static java.util.stream.Collectors.*; +import static java.util.stream.Collectors.joining; @Component @Transactional -@Log4j2 -@AllArgsConstructor public class EventManager { + private static final Logger log = LoggerFactory.getLogger(EventManager.class); + private static final Predicate<TicketCategory> IS_CATEGORY_BOUNDED = TicketCategory::isBounded; static final String ERROR_ONLINE_ON_SITE_NOT_COMPATIBLE = "Cannot switch to Online. Please remove On-Site payment method first."; private final UserManager userManager; @@ -123,6 +123,56 @@ public class EventManager { private final ClockProvider clockProvider; private final SubscriptionRepository subscriptionRepository; + public EventManager(UserManager userManager, + EventRepository eventRepository, + EventDescriptionRepository eventDescriptionRepository, + TicketCategoryRepository ticketCategoryRepository, + TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository, + TicketRepository ticketRepository, + SpecialPriceRepository specialPriceRepository, + PromoCodeDiscountRepository promoCodeRepository, + ConfigurationManager configurationManager, + TicketFieldRepository ticketFieldRepository, + EventDeleterRepository eventDeleterRepository, + AdditionalServiceRepository additionalServiceRepository, + AdditionalServiceTextRepository additionalServiceTextRepository, + Flyway flyway, + Environment environment, + OrganizationRepository organizationRepository, + AuditingRepository auditingRepository, + ExtensionManager extensionManager, + GroupRepository groupRepository, + NamedParameterJdbcTemplate jdbcTemplate, + ConfigurationRepository configurationRepository, + PaymentManager paymentManager, + ClockProvider clockProvider, + SubscriptionRepository subscriptionRepository) { + this.userManager = userManager; + this.eventRepository = eventRepository; + this.eventDescriptionRepository = eventDescriptionRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketCategoryDescriptionRepository = ticketCategoryDescriptionRepository; + this.ticketRepository = ticketRepository; + this.specialPriceRepository = specialPriceRepository; + this.promoCodeRepository = promoCodeRepository; + this.configurationManager = configurationManager; + this.ticketFieldRepository = ticketFieldRepository; + this.eventDeleterRepository = eventDeleterRepository; + this.additionalServiceRepository = additionalServiceRepository; + this.additionalServiceTextRepository = additionalServiceTextRepository; + this.flyway = flyway; + this.environment = environment; + this.organizationRepository = organizationRepository; + this.auditingRepository = auditingRepository; + this.extensionManager = extensionManager; + this.groupRepository = groupRepository; + this.jdbcTemplate = jdbcTemplate; + this.configurationRepository = configurationRepository; + this.paymentManager = paymentManager; + this.clockProvider = clockProvider; + this.subscriptionRepository = subscriptionRepository; + } + public Event getSingleEvent(String eventName, String username) { return getOptionalByName(eventName, username).orElseThrow(IllegalStateException::new); @@ -132,6 +182,10 @@ public EventAndOrganizationId getEventAndOrganizationId(String eventName, String return getOptionalEventAndOrganizationIdByName(eventName, username).orElseThrow(IllegalStateException::new); } + public List<Integer> getEventIdsBySlug(List<String> eventSlugs, int organizationId) { + return eventRepository.findIdsByShortNames(eventSlugs, organizationId); + } + public Optional<Event> getOptionalByName(String eventName, String username) { return eventRepository.findOptionalByShortName(eventName) .filter(checkOwnership(username, organizationRepository)); @@ -197,13 +251,15 @@ public void createEvent(EventModification em, String username) { .findFirst() .orElseThrow(); int eventId = insertEvent(em); + Optional<EventAndOrganizationId> srcEvent = getCopiedFrom(em, username); Event event = eventRepository.findById(eventId); createOrUpdateEventDescription(eventId, em); createAllAdditionalServices(eventId, em.getAdditionalServices(), event.getZoneId(), event.getCurrency()); createAdditionalFields(event, em); - createCategoriesForEvent(em, event); + createCategoriesForEvent(em, event, srcEvent); createAllTicketsForEvent(event, em); - createSubscriptionLinks(eventId, organization.getId(), em); + createSubscriptionLinks(eventId, organization.getId(), em.getLinkedSubscriptions()); + srcEvent.ifPresent(eventAndOrganizationId -> copySettings(event, eventAndOrganizationId)); extensionManager.handleEventCreation(event); var eventMetadata = extensionManager.handleMetadataUpdate(event, organization, AlfioMetadata.empty()); if(eventMetadata != null) { @@ -211,9 +267,28 @@ public void createEvent(EventModification em, String username) { } } - private void createSubscriptionLinks(int eventId, int organizationId, EventModification em) { - if(CollectionUtils.isNotEmpty(em.getLinkedSubscriptions())) { - var parameters = em.getLinkedSubscriptions().stream() + private Optional<EventAndOrganizationId> getCopiedFrom(EventModification em, String username) { + if (em.getMetadata() != null && StringUtils.isNotBlank(em.getMetadata().getCopiedFrom())) { + return getOptionalEventAndOrganizationIdByName(em.getMetadata().getCopiedFrom(), username); + } + return Optional.empty(); + } + + /** + * Copies settings from a copied event into the new one + * Supported settings are: + * - Event-specific configuration + * @param event event + * @param srcEvent source event + */ + private void copySettings(Event event, EventAndOrganizationId srcEvent) { + int count = configurationRepository.copyEventConfiguration(event.getId(), event.getOrganizationId(), srcEvent.getId(), srcEvent.getOrganizationId()); + log.info("copied {} settings from source event", count); + } + + private void createSubscriptionLinks(int eventId, int organizationId, List<UUID> linkedSubscriptions) { + if(CollectionUtils.isNotEmpty(linkedSubscriptions)) { + var parameters = linkedSubscriptions.stream() .map(id -> new MapSqlParameterSource("eventId", eventId) .addValue("subscriptionId", id) .addValue("pricePerTicket", 0) @@ -410,11 +485,15 @@ public void updateEventPrices(EventAndOrganizationId original, EventModification } } int organizationId = original.getOrganizationId(); - if(CollectionUtils.isNotEmpty(em.getLinkedSubscriptions())) { - int removed = subscriptionRepository.removeStaleSubscriptions(eventId, organizationId, em.getLinkedSubscriptions()); + updateLinkedSubscriptions(em.getLinkedSubscriptions(), eventId, organizationId); + } + + public void updateLinkedSubscriptions(List<UUID> linkedSubscriptions, int eventId, int organizationId) { + if(CollectionUtils.isNotEmpty(linkedSubscriptions)) { + int removed = subscriptionRepository.removeStaleSubscriptions(eventId, organizationId, linkedSubscriptions); log.trace("removed {} subscription links", removed); - createSubscriptionLinks(eventId, organizationId, em); - } else if (em.getLinkedSubscriptions() != null) { + createSubscriptionLinks(eventId, organizationId, linkedSubscriptions); + } else if (linkedSubscriptions != null) { // the user removed all the subscriptions int removed = subscriptionRepository.removeAllSubscriptionsForEvent(eventId, organizationId); log.trace("removed all subscription links ({}) for event {}", removed, eventId); @@ -631,7 +710,7 @@ private Stream<MapSqlParameterSource> generateTicketsForCategory(TicketCategory .map(ps -> buildTicketParams(event.getId(), creationDate, filteredTC, tc.getSrcPriceCts(), ps)); } - private void createCategoriesForEvent(EventModification em, Event event) { + private void createCategoriesForEvent(EventModification em, Event event, Optional<EventAndOrganizationId> srcEventOptional) { boolean freeOfCharge = em.isFreeOfCharge(); ZoneId zoneId = TimeZone.getTimeZone(event.getTimeZone()).toZoneId(); int eventId = event.getId(); @@ -665,6 +744,17 @@ private void createCategoriesForEvent(EventModification em, Event event) { final TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(category.getKey(), event.getId()); specialPriceRepository.bulkInsert(ticketCategory, ticketCategory.getMaxTickets()); } + + if (srcEventOptional.isPresent() && tc.getMetadata() != null && StringUtils.isNumeric(tc.getMetadata().getCopiedFrom())) { + int count = configurationRepository.copyCategoryConfiguration(event.getId(), + event.getOrganizationId(), + category.getKey(), + srcEventOptional.get().getId(), + srcEventOptional.get().getOrganizationId(), + Integer.parseInt(tc.getMetadata().getCopiedFrom()) + ); + log.info("Copied {} settings for category {}", count, tc.getName()); + } }); } @@ -707,19 +797,18 @@ void saveBadgeColorConfiguration(String badgeColor, Event event, Integer categor colorConfiguration = new CheckInOutputColorConfiguration("success", List.of(new ColorConfiguration(chosenColor, List.of(categoryId)))); } else { var configurationWithoutCategory = colorConfiguration.getConfigurations().stream() - .map(cc -> new ColorConfiguration(cc.getColorName(), cc.getCategories().stream().filter(c -> !c.equals(categoryId)).collect(toUnmodifiableList()))) - .filter(cc -> !cc.getCategories().isEmpty()) - .collect(toList()); + .map(cc -> new ColorConfiguration(cc.getColorName(), cc.getCategories().stream().filter(c -> !c.equals(categoryId)).toList())) + .filter(cc -> !cc.getCategories().isEmpty()).toList(); boolean colorExists = configurationWithoutCategory.stream().anyMatch(cc -> cc.getColorName().equals(chosenColor)); if(colorExists) { colorConfiguration = new CheckInOutputColorConfiguration(colorConfiguration.getDefaultColorName(), configurationWithoutCategory.stream().map(cc -> { - if(cc.getColorName().equals(chosenColor)) { + if (cc.getColorName().equals(chosenColor)) { var newList = new ArrayList<>(cc.getCategories()); newList.add(categoryId); return new ColorConfiguration(chosenColor, newList); } return cc; - }).collect(toUnmodifiableList())); + }).toList()); } else { var newList = new ArrayList<>(configurationWithoutCategory); newList.add(new ColorConfiguration(chosenColor, List.of(categoryId))); @@ -888,10 +977,11 @@ private int insertEvent(EventModification em) { String privateKey = UUID.randomUUID().toString(); ZoneId zoneId = ZoneId.of(em.getZoneId()); String currentVersion = flyway.info().current().getVersion().getVersion(); + var eventMetadata = requireNonNullElseGet(em.getMetadata(), AlfioMetadata::empty); return eventRepository.insert(em.getShortName(), em.getFormat(), em.getDisplayName(), em.getWebsiteUrl(), em.getExternalUrl(), em.getTermsAndConditionsUrl(), em.getPrivacyPolicyUrl(), em.getImageUrl(), em.getFileBlobId(), em.getLocation(), em.getLatitude(), em.getLongitude(), em.getBegin().toZonedDateTime(zoneId), em.getEnd().toZonedDateTime(zoneId), em.getZoneId(), em.getCurrency(), em.getAvailableSeats(), em.isVatIncluded(), - vat, paymentProxies, privateKey, em.getOrganizationId(), em.getLocales(), em.getVatStatus(), em.getPriceInCents(), currentVersion, Event.Status.DRAFT, em.getMetadata()).getKey(); + vat, paymentProxies, privateKey, em.getOrganizationId(), em.getLocales(), em.getVatStatus(), em.getPriceInCents(), currentVersion, Event.Status.DRAFT, eventMetadata).getKey(); } private String collectPaymentProxies(EventModification em) { @@ -934,12 +1024,14 @@ public void addPromoCode(String promoCode, String description, String emailReference, PromoCodeDiscount.CodeType codeType, - Integer hiddenCategoryId) { + Integer hiddenCategoryId, + String currencyCode) { Validate.isTrue(promoCode.length() >= 7, "min length is 7 chars"); Validate.isTrue((eventId != null && organizationId == null) || (eventId == null && organizationId != null), "eventId or organizationId must be not null"); Validate.isTrue(StringUtils.length(description) < 1025, "Description can be maximum 1024 chars"); Validate.isTrue(StringUtils.length(emailReference) < 257, "Description can be maximum 256 chars"); + Validate.isTrue(!PromoCodeDiscount.supportsCurrencyCode(codeType, discountType) || StringUtils.length(currencyCode) == 3, "Currency code is not valid"); if(maxUsage != null) { Validate.isTrue(maxUsage > 0, "Invalid max usage"); @@ -952,11 +1044,11 @@ public void addPromoCode(String promoCode, } if(PromoCodeDiscount.CodeType.ACCESS == codeType) { - Validate.isTrue(hiddenCategoryId != null, "Hidden category is required"); + Validate.notNull(hiddenCategoryId, "Hidden category is required"); } // - categoriesId = Optional.ofNullable(categoriesId).orElse(Collections.emptyList()).stream().filter(Objects::nonNull).collect(toList()); + categoriesId = Optional.ofNullable(categoriesId).orElse(Collections.emptyList()).stream().filter(Objects::nonNull).toList(); // if (organizationId == null) { @@ -967,7 +1059,8 @@ public void addPromoCode(String promoCode, discountType = DiscountType.NONE; } - promoCodeRepository.addPromoCode(promoCode, eventId, organizationId, start, end, discountAmount, discountType, Json.GSON.toJson(categoriesId), maxUsage, description, emailReference, codeType, hiddenCategoryId); + promoCodeRepository.addPromoCode(promoCode, eventId, organizationId, start, end, discountAmount, discountType, + Json.GSON.toJson(categoriesId), maxUsage, description, emailReference, codeType, hiddenCategoryId, currencyCode); } public void deletePromoCode(int promoCodeId) { @@ -984,12 +1077,12 @@ public void updatePromoCode(int promoCodeId, ZonedDateTime start, ZonedDateTime public List<PromoCodeDiscountWithFormattedTimeAndAmount> findPromoCodesInEvent(int eventId) { var event = eventRepository.findById(eventId); - return promoCodeRepository.findAllInEvent(eventId).stream().map(p -> new PromoCodeDiscountWithFormattedTimeAndAmount(p, event.getZoneId(), event.getCurrency())).collect(toList()); + return promoCodeRepository.findAllInEvent(eventId).stream().map(p -> new PromoCodeDiscountWithFormattedTimeAndAmount(p, event.getZoneId(), event.getCurrency())).toList(); } public List<PromoCodeDiscountWithFormattedTimeAndAmount> findPromoCodesInOrganization(int organizationId) { ZoneId zoneId = ZoneId.systemDefault(); - return promoCodeRepository.findAllInOrganization(organizationId).stream().map(p -> new PromoCodeDiscountWithFormattedTimeAndAmount(p, zoneId, null)).collect(toList()); + return promoCodeRepository.findAllInOrganization(organizationId).stream().map(p -> new PromoCodeDiscountWithFormattedTimeAndAmount(p, zoneId, null)).toList(); } public String getEventUrl(Event event) { @@ -1011,7 +1104,7 @@ public List<Event> getPublishedEvents(SearchOptions searchOptions) { } public List<Event> getActiveEvents() { - return getActiveEventsStream().collect(toList()); + return getActiveEventsStream().toList(); } private Stream<Event> getActiveEventsStream() { @@ -1085,6 +1178,9 @@ public void deleteCategory(String eventName, int categoryId, String username) { throw new IllegalArgumentException("Event not found"); } int eventId = optionalEvent.get().getId(); + if(ticketCategoryRepository.countActiveByEventId(eventId) < 2) { + throw new IllegalArgumentException("At least one category is required"); + } var optionalCategory = getOptionalByIdAndActive(categoryId, eventId); if(optionalCategory.isEmpty()) { throw new IllegalArgumentException("Category not found"); @@ -1135,13 +1231,15 @@ public Map<Integer, String> getEventsNameInOrganization(int orgId, Principal pri } public boolean updateMetadata(Event event, AlfioMetadata metadata) { + var existing = eventRepository.getMetadataForEvent(event.getId()); var updatedMetadata = extensionManager.handleMetadataUpdate(event, organizationRepository.getById(event.getOrganizationId()), metadata); - eventRepository.updateMetadata(Objects.requireNonNullElse(updatedMetadata, metadata), event.getId()); + eventRepository.updateMetadata(existing.merge(Objects.requireNonNullElse(updatedMetadata, metadata)), event.getId()); return true; } public boolean updateCategoryMetadata(EventAndOrganizationId event, int categoryId, AlfioMetadata metadata) { - return ticketCategoryRepository.updateMetadata(metadata, event.getId(), categoryId) == 1; + var existing = ticketCategoryRepository.getMetadata(event.getId(), categoryId); + return ticketCategoryRepository.updateMetadata(existing.merge(metadata), event.getId(), categoryId) == 1; } public AlfioMetadata getMetadataForEvent(EventAndOrganizationId event) { @@ -1172,5 +1270,13 @@ public Optional<String> executeCapability(String eventName, }); } + public List<UUID> getLinkedSubscriptionIds(int eventId, int organizationId) { + return subscriptionRepository.findLinkedSubscriptionIds(eventId, organizationId); + } + + public int getEventsCount() { + return eventRepository.countEvents(); + } + } diff --git a/src/main/java/alfio/manager/EventNameManager.java b/src/main/java/alfio/manager/EventNameManager.java index c6275e20ef..881de6c233 100644 --- a/src/main/java/alfio/manager/EventNameManager.java +++ b/src/main/java/alfio/manager/EventNameManager.java @@ -17,7 +17,6 @@ package alfio.manager; import alfio.repository.EventAdminRepository; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; @@ -34,7 +33,6 @@ import java.util.stream.IntStream; @Component -@AllArgsConstructor @Transactional(readOnly = true) public class EventNameManager { @@ -48,6 +46,10 @@ public class EventNameManager { .build(); private final EventAdminRepository eventAdminRepository; + public EventNameManager(EventAdminRepository eventAdminRepository) { + this.eventAdminRepository = eventAdminRepository; + } + /** * Generates and returns a short name based on the given display name.<br> * The generated short name will be returned only if it was not already used.<br> diff --git a/src/main/java/alfio/manager/EventStatisticsManager.java b/src/main/java/alfio/manager/EventStatisticsManager.java index 6cfdf96bb3..3444098789 100644 --- a/src/main/java/alfio/manager/EventStatisticsManager.java +++ b/src/main/java/alfio/manager/EventStatisticsManager.java @@ -25,7 +25,6 @@ import alfio.repository.*; import alfio.util.EventUtil; import alfio.util.MonetaryUtil; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -39,12 +38,10 @@ import java.util.stream.Stream; import static alfio.model.system.ConfigurationKeys.DISPLAY_STATS_IN_EVENT_DETAIL; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.apache.commons.lang3.ObjectUtils.firstNonNull; @Component -@AllArgsConstructor @Transactional(readOnly = true) public class EventStatisticsManager { @@ -60,14 +57,38 @@ public class EventStatisticsManager { private final SubscriptionRepository subscriptionRepository; private final ExtensionManager extensionManager; + public EventStatisticsManager(EventRepository eventRepository, + EventDescriptionRepository eventDescriptionRepository, + TicketSearchRepository ticketSearchRepository, + TicketCategoryRepository ticketCategoryRepository, + TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository, + TicketReservationRepository ticketReservationRepository, + SpecialPriceRepository specialPriceRepository, + ConfigurationManager configurationManager, + UserManager userManager, + SubscriptionRepository subscriptionRepository, + ExtensionManager extensionManager) { + this.eventRepository = eventRepository; + this.eventDescriptionRepository = eventDescriptionRepository; + this.ticketSearchRepository = ticketSearchRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketCategoryDescriptionRepository = ticketCategoryDescriptionRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.specialPriceRepository = specialPriceRepository; + this.configurationManager = configurationManager; + this.userManager = userManager; + this.subscriptionRepository = subscriptionRepository; + this.extensionManager = extensionManager; + } + private List<Event> getAllEvents(String username) { - List<Integer> orgIds = userManager.findUserOrganizations(username).stream().map(Organization::getId).collect(toList()); + List<Integer> orgIds = userManager.findUserOrganizations(username).stream().map(Organization::getId).toList(); return orgIds.isEmpty() ? Collections.emptyList() : eventRepository.findByOrganizationIds(orgIds); } public List<EventStatistic> getAllEventsWithStatisticsFilteredBy(String username, Predicate<Event> predicate) { - List<Event> events = getAllEvents(username).stream().filter(predicate).collect(toList()); + List<Event> events = getAllEvents(username).stream().filter(predicate).toList(); Map<Integer, Event> mappedEvent = events.stream().collect(Collectors.toMap(Event::getId, Function.identity())); if(!mappedEvent.isEmpty()) { boolean isOwner = userManager.isOwner(userManager.findUserByUsername(username)); @@ -81,7 +102,7 @@ public List<EventStatistic> getAllEventsWithStatisticsFilteredBy(String username return stats.map(stat -> { Event event = mappedEvent.get(stat.getEventId()); return new EventStatistic(event, stat, displayStatisticsForEvent(event)); - }).collect(Collectors.toList()); + }).toList(); } else { return Collections.emptyList(); } @@ -105,7 +126,7 @@ public EventWithAdditionalInfo getEventWithAdditionalInfo(String eventName, Stri BigDecimal grossIncome = owner ? MonetaryUtil.centsToUnit(eventRepository.getGrossIncome(event.getId()), event.getCurrency()) : BigDecimal.ZERO; List<TicketCategory> ticketCategories = ticketCategoryRepository.findAllTicketCategories(event.getId()); - List<Integer> ticketCategoriesIds = ticketCategories.stream().map(TicketCategory::getId).collect(Collectors.toList()); + List<Integer> ticketCategoriesIds = ticketCategories.stream().map(TicketCategory::getId).toList(); Map<Integer, Map<String, String>> descriptions = ticketCategoryDescriptionRepository.descriptionsByTicketCategory(ticketCategoriesIds); Map<Integer, TicketCategoryStatisticView> ticketCategoriesStatistics = owner ? ticketCategoryRepository.findStatisticsForEventIdByCategoryId(event.getId()) : ticketCategoriesIds.stream().collect(toMap(Function.identity(), id -> TicketCategoryStatisticView.empty(id, event.getId()))); @@ -115,7 +136,7 @@ public EventWithAdditionalInfo getEventWithAdditionalInfo(String eventName, Stri List<TicketCategoryWithAdditionalInfo> tWithInfo = ticketCategories.stream() .map(t -> new TicketCategoryWithAdditionalInfo(event, t, ticketCategoriesStatistics.get(t.getId()), descriptions.get(t.getId()), specialPrices.get(t.getId()), metadata.get(t.getId()))) - .collect(Collectors.toList()); + .toList(); Set<ExtensionCapabilitySummary> supportedCapabilities; if(event.getFormat() != Event.EventFormat.IN_PERSON) { @@ -124,6 +145,8 @@ public EventWithAdditionalInfo getEventWithAdditionalInfo(String eventName, Stri supportedCapabilities = Set.of(); } + // TODO category and event settings + return new EventWithAdditionalInfo(event, tWithInfo, eventStatistic, diff --git a/src/main/java/alfio/manager/ExportManager.java b/src/main/java/alfio/manager/ExportManager.java new file mode 100644 index 0000000000..bf1845d9bf --- /dev/null +++ b/src/main/java/alfio/manager/ExportManager.java @@ -0,0 +1,60 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager; + +import alfio.manager.user.UserManager; +import alfio.model.ReservationsByEvent; +import alfio.model.user.Organization; +import alfio.repository.ExportRepository; +import alfio.util.ClockProvider; +import org.springframework.stereotype.Component; + +import java.security.Principal; +import java.time.LocalDate; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Component +public class ExportManager { + + private final ExportRepository exportRepository; + private final ClockProvider clockProvider; + private final UserManager userManager; + + public ExportManager(ExportRepository exportRepository, + ClockProvider clockProvider, + UserManager userManager) { + this.exportRepository = exportRepository; + this.clockProvider = clockProvider; + this.userManager = userManager; + } + + public List<ReservationsByEvent> reservationsForInterval(LocalDate from, LocalDate to, Principal principal) { + if (from.isAfter(to)) { + throw new IllegalArgumentException("Wrong interval"); + } + var orgIds = userManager.findUserOrganizations(Objects.requireNonNull(principal).getName()); + if (orgIds.isEmpty()) { + throw new IllegalArgumentException("Wrong user"); + } + var zoneId = clockProvider.getClock().getZone(); + var zonedFrom = from.atStartOfDay().atZone(zoneId); + var zonedTo = to.plusDays(1).atStartOfDay().minusSeconds(1).atZone(zoneId); + return exportRepository.allReservationsForInterval(zonedFrom, zonedTo, orgIds.stream().map(Organization::getId).collect(Collectors.toList())); + } +} diff --git a/src/main/java/alfio/manager/ExtensionManager.java b/src/main/java/alfio/manager/ExtensionManager.java index d5931bafd8..14fcfe7c11 100644 --- a/src/main/java/alfio/manager/ExtensionManager.java +++ b/src/main/java/alfio/manager/ExtensionManager.java @@ -18,6 +18,7 @@ package alfio.manager; import alfio.config.authentication.support.OpenIdAlfioAuthentication; +import alfio.controller.form.ContactAndTicketsForm; import alfio.extension.ExtensionService; import alfio.extension.exception.AlfioScriptingException; import alfio.manager.payment.PaymentSpecification; @@ -30,21 +31,21 @@ import alfio.model.checkin.EventWithCheckInInfo; import alfio.model.extension.*; import alfio.model.metadata.AlfioMetadata; +import alfio.model.metadata.SubscriptionMetadata; import alfio.model.metadata.TicketMetadata; import alfio.model.metadata.TicketMetadataContainer; +import alfio.model.subscription.Subscription; +import alfio.model.subscription.SubscriptionDescriptor; import alfio.model.system.ConfigurationKeys; import alfio.model.user.Organization; import alfio.model.user.PublicUserProfile; import alfio.model.user.User; -import alfio.repository.EventRepository; -import alfio.repository.TicketRepository; -import alfio.repository.TicketReservationRepository; -import alfio.repository.TransactionRepository; +import alfio.repository.*; import alfio.util.ClockProvider; import alfio.util.EventUtil; import alfio.util.MonetaryUtil; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; @@ -55,16 +56,18 @@ import java.nio.file.Paths; import java.time.ZonedDateTime; import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; import static alfio.extension.ExtensionService.toPath; import static alfio.manager.support.extension.ExtensionEvent.*; import static alfio.model.PromoCodeDiscount.DiscountType.PERCENTAGE; @Component -@AllArgsConstructor -@Log4j2 public class ExtensionManager { + private static final Logger log = LoggerFactory.getLogger(ExtensionManager.class); + private static final String TICKET = "ticket"; private static final String EVENT_METADATA = "eventMetadata"; private static final String ORGANIZATION = "organization"; @@ -83,6 +86,23 @@ public class ExtensionManager { private final TicketRepository ticketRepository; private final ConfigurationManager configurationManager; private final TransactionRepository transactionRepository; + private final TicketCategoryRepository ticketCategoryRepository; + + public ExtensionManager(ExtensionService extensionService, + EventRepository eventRepository, + TicketReservationRepository ticketReservationRepository, + TicketRepository ticketRepository, + ConfigurationManager configurationManager, + TransactionRepository transactionRepository, + TicketCategoryRepository ticketCategoryRepository) { + this.extensionService = extensionService; + this.eventRepository = eventRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.ticketRepository = ticketRepository; + this.configurationManager = configurationManager; + this.transactionRepository = transactionRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + } boolean isSupported(ExtensionCapability extensionCapability, PurchaseContext purchaseContext) { @@ -153,8 +173,8 @@ public void handleTicketAssignment(Ticket ticket, event, Map.of( TICKET, ticket, - ADDITIONAL_INFO, additionalInfo, - EVENT_METADATA, eventRepository.getMetadataForEvent(event.getId()), + ADDITIONAL_INFO, Objects.requireNonNullElse(additionalInfo, Map.of()), + EVENT_METADATA, Objects.requireNonNullElseGet(eventRepository.getMetadataForEvent(event.getId()), AlfioMetadata::empty), "onlineAccessTicket", EventUtil.isAccessOnline(category, event) )); } @@ -193,7 +213,7 @@ void handleStuckReservations(Event event, List<String> stuckReservationsId) { asyncCall(ExtensionEvent.STUCK_RESERVATIONS, event, payload); } - Optional<CustomEmailText> handleReservationEmailCustomText(PurchaseContext purchaseContext, TicketReservation reservation, TicketReservationAdditionalInfo additionalInfo) { + public Optional<CustomEmailText> handleReservationEmailCustomText(PurchaseContext purchaseContext, TicketReservation reservation, TicketReservationAdditionalInfo additionalInfo) { Map<String, Object> payload = Map.of( RESERVATION, reservation, "purchaseContext", purchaseContext, @@ -255,7 +275,7 @@ public Optional<InvoiceGeneration> handleInvoiceGeneration(PaymentSpecification payload.put("vatNr", billingDetails.getTaxId()); payload.put("vatStatus", spec.getVatStatus()); - return Optional.ofNullable(syncCall(ExtensionEvent.INVOICE_GENERATION, spec.getPurchaseContext(), payload, InvoiceGeneration.class)); + return Optional.ofNullable(syncCall(ExtensionEvent.INVOICE_GENERATION, spec.getPurchaseContext(), payload, InvoiceGeneration.class, false)); } public Optional<CreditNoteGeneration> handleCreditNoteGeneration(PurchaseContext purchaseContext, @@ -347,8 +367,8 @@ void handleReservationsCreditNoteIssuedForEvent(Event event, List<String> reserv void handleRefund(PurchaseContext purchaseContext, TicketReservation reservation, TransactionAndPaymentInfo info) { Map<String, Object> payload = new HashMap<>(); payload.put(RESERVATION, reservation); - payload.put("transaction", info.getTransaction()); - payload.put("paymentInfo", info.getPaymentInformation()); + payload.put("transaction", info.transaction()); + payload.put("paymentInfo", info.paymentInformation()); asyncCall(ExtensionEvent.REFUND_ISSUED, purchaseContext, payload); } @@ -381,7 +401,7 @@ public boolean handlePdfTransformation(String html, PurchaseContext purchaseCont if(response == null || response.isEmpty()) { return false; } - Path tempFilePath = Paths.get(response.getTempFilePath()); + Path tempFilePath = Paths.get(response.tempFilePath()); if(Files.exists(tempFilePath)) { Files.copy(tempFilePath, outputStream); Files.delete(tempFilePath); @@ -422,7 +442,7 @@ public Optional<PromoCodeDiscount> handleDynamicDiscount(Event event, Map<Intege return Optional.of(new PromoCodeDiscount(Integer.MIN_VALUE, dynamicDiscountResult.getCode(), event.getId(), event.getOrganizationId(), now.minusSeconds(1), event.getBegin().withZoneSameInstant(now.getZone()), discountAmountInCents, dynamicDiscountResult.getDiscountType(), null, null, null, - null, CodeType.DYNAMIC, null)); + null, CodeType.DYNAMIC, null, event.getCurrency())); } catch(Exception ex) { log.warn("got exception while firing DYNAMIC_DISCOUNT_APPLICATION event", ex); } @@ -524,4 +544,35 @@ public Optional<TicketMetadata> handleTicketAssignmentMetadata(TicketWithMetadat context.put(TICKET_METADATA, ticketWithMetadata.getMetadata().getMetadataForKey(TicketMetadataContainer.GENERAL).orElseGet(TicketMetadata::empty)); return Optional.ofNullable(syncCall(ExtensionEvent.TICKET_ASSIGNED_GENERATE_METADATA, event, context, TicketMetadata.class, false)); } + + public Optional<SubscriptionMetadata> handleSubscriptionAssignmentMetadata(Subscription subscription, + SubscriptionDescriptor descriptor, + SubscriptionMetadata subscriptionMetadata) { + var context = new HashMap<String, Object>(); + context.put("subscription", subscription); + context.put("metadata", Objects.requireNonNullElseGet(subscriptionMetadata, SubscriptionMetadata::empty)); + context.put("subscriptionDescriptor", descriptor); + return Optional.ofNullable(syncCall(ExtensionEvent.SUBSCRIPTION_ASSIGNED_GENERATE_METADATA, descriptor, context, SubscriptionMetadata.class, false)); + } + + public Optional<CustomTaxPolicy> handleCustomTaxPolicy(PurchaseContext purchaseContext, + String reservationId, + ContactAndTicketsForm form, + TotalPrice reservationCost) { + if (!purchaseContext.ofType(PurchaseContext.PurchaseContextType.event) || !reservationCost.requiresPayment()) { + return Optional.empty(); + } + var event = (Event) purchaseContext; + var categoriesById = ticketCategoryRepository.findCategoriesInReservation(reservationId).stream() + .collect(Collectors.toMap(TicketCategory::getId, Function.identity())); + var ticketInfoById = ticketRepository.findBasicTicketInfoForReservation(event.getId(), reservationId).stream() + .collect(Collectors.toMap(TicketInfo::getTicketUuid, Function.identity())); + var context = new HashMap<String, Object>(); + context.put(EVENT, event); + context.put(RESERVATION_ID, reservationId); + context.put("reservationForm", form); + context.put("categoriesById", categoriesById); + context.put("ticketInfoByUuid", ticketInfoById); + return Optional.ofNullable(syncCall(CUSTOM_TAX_POLICY_APPLICATION, event, context, CustomTaxPolicy.class, false)); + } } diff --git a/src/main/java/alfio/manager/FileDownloadManager.java b/src/main/java/alfio/manager/FileDownloadManager.java index 216d268a32..ec4263d0ee 100644 --- a/src/main/java/alfio/manager/FileDownloadManager.java +++ b/src/main/java/alfio/manager/FileDownloadManager.java @@ -17,22 +17,23 @@ package alfio.manager; import alfio.model.modification.UploadBase64FileModification; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.Arrays; import java.util.Objects; import java.util.regex.Pattern; -@Log4j2 public class FileDownloadManager { + private static final Logger log = LoggerFactory.getLogger(FileDownloadManager.class); + private final HttpClient httpClient; public FileDownloadManager(HttpClient httpClient) { @@ -41,7 +42,7 @@ public FileDownloadManager(HttpClient httpClient) { public DownloadedFile downloadFile(String url) { HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(url)).GET().build(); - HttpResponse<byte[]> response = null; + HttpResponse<byte[]> response; try { response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); } catch (IOException exception) { @@ -55,7 +56,7 @@ public DownloadedFile downloadFile(String url) { if(callSuccessful(response)) { String[] parts = Pattern.compile("/").split(url); String name = parts[parts.length - 1]; - if(Objects.nonNull(response.body()) && response.body().length <= FileUploadManager.MAXIMUM_ALLOWED_SIZE) { + if(Objects.nonNull(response.body())) { return new DownloadedFile( response.body(), name, @@ -65,7 +66,7 @@ public DownloadedFile downloadFile(String url) { return null; } } else { - log.warn("downloading file not successful:" + response); + log.warn("downloading file not successful: {}", response); return null; } } @@ -78,12 +79,8 @@ private boolean callSuccessful(HttpResponse<?> response) { return response.statusCode() >= 200 && response.statusCode() < 300; } - @Getter - @AllArgsConstructor - public static class DownloadedFile { - private byte[] file; - private String name; - private String type; + + public record DownloadedFile(byte[] file, String name, String type) { public UploadBase64FileModification toUploadBase64FileModification() { UploadBase64FileModification uf = new UploadBase64FileModification(); @@ -92,6 +89,33 @@ public UploadBase64FileModification toUploadBase64FileModification() { uf.setType(type); return uf; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DownloadedFile df)) { + return false; + } + return Arrays.equals(file, df.file) && name.equals(df.name) && type.equals(df.type); + } + + @Override + public int hashCode() { + int result = Objects.hash(name, type); + result = 31 * result + Arrays.hashCode(file); + return result; + } + + @Override + public String toString() { + return "DownloadedFile{" + + "file=" + Arrays.toString(file) + + ", name='" + name + '\'' + + ", type='" + type + '\'' + + '}'; + } } } diff --git a/src/main/java/alfio/manager/FileUploadManager.java b/src/main/java/alfio/manager/FileUploadManager.java index 8bf27f5dab..888f271558 100644 --- a/src/main/java/alfio/manager/FileUploadManager.java +++ b/src/main/java/alfio/manager/FileUploadManager.java @@ -22,42 +22,61 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; +import com.github.benmanes.caffeine.cache.RemovalListener; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.imgscalr.Scalr; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.MimeType; +import org.springframework.util.MimeTypeUtils; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; +import java.nio.file.Files; import java.time.Duration; import java.util.*; @Component @Transactional -@Log4j2 -@RequiredArgsConstructor public class FileUploadManager { + static final int IMAGE_THUMB_MAX_WIDTH_PX = 300; + static final int IMAGE_THUMB_MAX_HEIGHT_PX = 200; + private static final Logger log = LoggerFactory.getLogger(FileUploadManager.class); /** * Maximum allowed file size is 200kb */ - static final int MAXIMUM_ALLOWED_SIZE = 1024 * 200; + private static final int MAXIMUM_ALLOWED_SIZE = 1024 * 200; + private static final MimeType IMAGE_TYPE = MimeType.valueOf("image/*"); private final FileUploadRepository repository; private final Cache<String, File> cache = Caffeine.newBuilder() .maximumSize(20) .expireAfterWrite(Duration.ofMinutes(20)) - .removalListener((String key, File value, RemovalCause cause) -> { - if(value != null) { - boolean result = value.delete(); - log.trace("deleted {}: {}", key, result); - } - }) + .removalListener(removalListener()) .build(); + public FileUploadManager(FileUploadRepository repository) { + this.repository = repository; + } + + private static RemovalListener<String, File> removalListener() { + return (String key, File value, RemovalCause cause) -> { + if (value != null) { + try { + Files.delete(value.toPath()); + log.trace("deleted {}", key); + } catch(Exception ex) { + log.trace("Error while deleting file", ex); + } + } + }; + } + public Optional<FileBlobMetadata> findMetadata(String id) { return repository.findById(id); } @@ -79,12 +98,13 @@ public void outputFile(String id, OutputStream out) { } } - public String insertFile(UploadBase64FileModification file) { - Validate.exclusiveBetween(1, MAXIMUM_ALLOWED_SIZE, file.getFile().length); - String digest = DigestUtils.sha256Hex(file.getFile()); + final var mimeType = MimeTypeUtils.parseMimeType(file.getType()); + var upload = resizeIfNeeded(file, mimeType); + Validate.exclusiveBetween(1, MAXIMUM_ALLOWED_SIZE, upload.getFile().length); + String digest = DigestUtils.sha256Hex(upload.getFile()); if (Integer.valueOf(0).equals(repository.isPresent(digest))) { - repository.upload(file, digest, getAttributes(file)); + repository.upload(upload, digest, getAttributes(upload)); } return digest; } @@ -94,6 +114,35 @@ public void cleanupUnreferencedBlobFiles(Date date) { log.debug("removed {} unused file_blob", deleted); } + /** + * @author <a href="https://github.com/emassip">Etienne M.</a> + */ + private UploadBase64FileModification resizeIfNeeded(UploadBase64FileModification upload, MimeType mimeType) { + if (!mimeType.isCompatibleWith(IMAGE_TYPE)) { + // not an image, nothing to do here. + return upload; + } + try { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(upload.getFile())); + // resize only if the image is bigger than target size on either side + if (image.getWidth() > IMAGE_THUMB_MAX_WIDTH_PX || image.getHeight() > IMAGE_THUMB_MAX_HEIGHT_PX) { + UploadBase64FileModification resized = new UploadBase64FileModification(); + BufferedImage thumbImg = Scalr.resize(image, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, IMAGE_THUMB_MAX_WIDTH_PX, IMAGE_THUMB_MAX_HEIGHT_PX, Scalr.OP_ANTIALIAS); + try (final var baos = new ByteArrayOutputStream()) { + ImageIO.write(thumbImg, mimeType.getSubtype(), baos); + resized.setFile(baos.toByteArray()); + } + resized.setAttributes(upload.getAttributes()); + resized.setName(upload.getName()); + resized.setType(upload.getType()); + return resized; + } + return upload; + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + private Map<String, String> getAttributes(UploadBase64FileModification file) { if(!StringUtils.startsWith(file.getType(), "image/")) { return Collections.emptyMap(); diff --git a/src/main/java/alfio/manager/GroupManager.java b/src/main/java/alfio/manager/GroupManager.java index 25e41dcae7..cc89a03017 100644 --- a/src/main/java/alfio/manager/GroupManager.java +++ b/src/main/java/alfio/manager/GroupManager.java @@ -30,11 +30,11 @@ import alfio.repository.GroupRepository; import alfio.repository.TicketRepository; import ch.digitalfondue.npjt.AffectedRowCountAndKey; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -49,11 +49,13 @@ import static alfio.model.group.LinkedGroup.MatchType.FULL; import static alfio.model.group.LinkedGroup.Type.*; import static java.util.Collections.singletonList; +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4; @Component -@Log4j2 public class GroupManager { + private static final Logger log = LoggerFactory.getLogger(GroupManager.class); + private final GroupRepository groupRepository; private final TicketRepository ticketRepository; private final AuditingRepository auditingRepository; @@ -81,7 +83,7 @@ public Result<Integer> createNew(GroupModification input) { } Group createNew(String name, String description, int organizationId) { - AffectedRowCountAndKey<Integer> insert = groupRepository.insert(name, description, organizationId); + AffectedRowCountAndKey<Integer> insert = groupRepository.insert(escapeHtml4(name), escapeHtml4(description), organizationId); return groupRepository.getById(insert.getKey()); } @@ -130,7 +132,7 @@ public List<Group> getAllForOrganization(int organizationId) { public Optional<GroupModification> loadComplete(int id) { return groupRepository.getOptionalById(id) .map(wl -> { - List<GroupMemberModification> items = groupRepository.getItems(wl.getId()).stream().map(i -> new GroupMemberModification(i.getId(), i.getValue(), i.getDescription())).collect(Collectors.toList()); + List<GroupMemberModification> items = groupRepository.getItems(wl.getId()).stream().map(i -> new GroupMemberModification(i.getId(), i.getValue(), i.getDescription())).toList(); return new GroupModification(wl.getId(), wl.getName(), wl.getDescription(), wl.getOrganizationId(), items); }); } @@ -164,7 +166,7 @@ public List<LinkedGroup> findLinks(int eventId, int categoryId) { Result<Integer> insertMembers(int groupId, List<GroupMemberModification> members) { Map<String, List<GroupMemberModification>> grouped = members.stream().collect(Collectors.groupingBy(GroupMemberModification::getValue)); - List<String> duplicates = grouped.entrySet().stream().filter(e -> e.getValue().size() > 1).map(Map.Entry::getKey).collect(Collectors.toList()); + List<String> duplicates = grouped.entrySet().stream().filter(e -> e.getValue().size() > 1).map(Map.Entry::getKey).toList(); return new Result.Builder<Integer>() .checkPrecondition(duplicates::isEmpty, ErrorCode.lazy(() -> ErrorCode.custom("value.duplicate", duplicates.stream().limit(10).collect(Collectors.joining(", "))))) @@ -215,7 +217,7 @@ private Optional<GroupMember> getMatchingMember(LinkedGroup configuration, Strin @Transactional public void deleteWhitelistedTicketsForReservation(String reservationId) { - List<Integer> tickets = ticketRepository.findTicketsInReservation(reservationId).stream().map(Ticket::getId).collect(Collectors.toList()); + List<Integer> tickets = ticketRepository.findTicketsInReservation(reservationId).stream().map(Ticket::getId).toList(); if(!tickets.isEmpty()) { int result = groupRepository.deleteExistingWhitelistedTickets(tickets); log.trace("deleted {} whitelisted tickets for reservation {}", result, reservationId); @@ -238,7 +240,7 @@ public Optional<GroupModification> update(int listId, GroupModification modifica List<GroupMemberModification> notPresent = modification.getItems().stream() .filter(i -> i.getId() == null && !existingValues.contains(i.getValue().strip().toLowerCase())) .distinct() - .collect(Collectors.toList()); + .toList(); if(!notPresent.isEmpty()) { var insertResult = insertMembers(listId, notPresent); @@ -247,7 +249,7 @@ public Optional<GroupModification> update(int listId, GroupModification modifica throw new DuplicateGroupItemException(error.getDescription()); } } - groupRepository.update(listId, modification.getName(), modification.getDescription()); + groupRepository.update(listId, escapeHtml4(modification.getName()), escapeHtml4(modification.getDescription())); return loadComplete(listId); } @@ -262,7 +264,7 @@ public boolean deactivateMembers(List<Integer> memberIds, int groupId) { @Transactional public boolean deactivateGroup(int groupId) { - List<Integer> members = groupRepository.getItems(groupId).stream().map(GroupMember::getId).collect(Collectors.toList()); + List<Integer> members = groupRepository.getItems(groupId).stream().map(GroupMember::getId).toList(); if(!members.isEmpty()) { Validate.isTrue(deactivateMembers(members, groupId), "error while disabling group members"); } @@ -271,11 +273,9 @@ public boolean deactivateGroup(int groupId) { return true; } - @RequiredArgsConstructor - public static class WhitelistValidator implements Predicate<WhitelistValidationItem> { - private final int eventId; - private final GroupManager groupManager; + public record WhitelistValidator(int eventId, + GroupManager groupManager) implements Predicate<WhitelistValidationItem> { @Override public boolean test(WhitelistValidationItem item) { @@ -283,10 +283,7 @@ public boolean test(WhitelistValidationItem item) { } } - @RequiredArgsConstructor - public static class WhitelistValidationItem { - private final int categoryId; - private final String value; + public record WhitelistValidationItem(int categoryId, String value) { } public static class DuplicateGroupItemException extends RuntimeException { diff --git a/src/main/java/alfio/manager/NotificationManager.java b/src/main/java/alfio/manager/NotificationManager.java index af9c806d62..8783f46fef 100644 --- a/src/main/java/alfio/manager/NotificationManager.java +++ b/src/main/java/alfio/manager/NotificationManager.java @@ -26,6 +26,7 @@ import alfio.manager.system.Mailer; import alfio.model.*; import alfio.model.PurchaseContext.PurchaseContextType; +import alfio.model.metadata.SubscriptionMetadata; import alfio.model.subscription.SubscriptionDescriptor; import alfio.model.system.ConfigurationKeys; import alfio.model.user.Organization; @@ -34,10 +35,11 @@ import alfio.util.*; import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.*; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.security.crypto.codec.Hex; @@ -69,9 +71,10 @@ import static java.util.Objects.requireNonNullElseGet; @Component -@Log4j2 public class NotificationManager { + private static final Logger log = LoggerFactory.getLogger(NotificationManager.class); + private static final String EVENT_ID = "eventId"; private final Mailer mailer; private final MessageSourceManager messageSourceManager; @@ -104,7 +107,8 @@ public NotificationManager(Mailer mailer, AdditionalServiceItemRepository additionalServiceItemRepository, ExtensionManager extensionManager, ClockProvider clockProvider, - PurchaseContextManager purchaseContextManager) { + PurchaseContextManager purchaseContextManager, + SubscriptionRepository subscriptionRepository) { this.messageSourceManager = messageSourceManager; this.mailer = mailer; this.emailMessageRepository = emailMessageRepository; @@ -127,7 +131,8 @@ public NotificationManager(Mailer mailer, payload -> TemplateProcessor.buildCreditNotePdf(payload.getLeft(), fileUploadManager, payload.getMiddle(), templateManager, payload.getRight(), extensionManager))); attachmentTransformer.put(Mailer.AttachmentIdentifier.PASSBOOK, passKitManager::getPass); Function<Ticket, List<TicketFieldConfigurationDescriptionAndValue>> retrieveFieldValues = EventUtil.retrieveFieldValues(ticketRepository, ticketFieldRepository, additionalServiceItemRepository); - attachmentTransformer.put(Mailer.AttachmentIdentifier.TICKET_PDF, generateTicketPDF(eventRepository, organizationRepository, configurationManager, fileUploadManager, templateManager, ticketReservationRepository, retrieveFieldValues, extensionManager, ticketRepository)); + attachmentTransformer.put(Mailer.AttachmentIdentifier.TICKET_PDF, generateTicketPDF(eventRepository, organizationRepository, configurationManager, fileUploadManager, templateManager, ticketReservationRepository, retrieveFieldValues, extensionManager, ticketRepository, subscriptionRepository)); + attachmentTransformer.put(Mailer.AttachmentIdentifier.SUBSCRIPTION_PDF, generateSubscriptionPDF(organizationRepository, configurationManager, fileUploadManager, templateManager, ticketReservationRepository, extensionManager, subscriptionRepository)); } private static Function<Map<String, String>, byte[]> generateTicketPDF(EventRepository eventRepository, @@ -138,7 +143,8 @@ private static Function<Map<String, String>, byte[]> generateTicketPDF(EventRepo TicketReservationRepository ticketReservationRepository, Function<Ticket, List<TicketFieldConfigurationDescriptionAndValue>> retrieveFieldValues, ExtensionManager extensionManager, - TicketRepository ticketRepository) { + TicketRepository ticketRepository, + SubscriptionRepository subscriptionRepository) { return model -> { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Ticket ticket = Json.fromJson(model.get("ticket"), Ticket.class); @@ -148,11 +154,13 @@ private static Function<Map<String, String>, byte[]> generateTicketPDF(EventRepo Event event = eventRepository.findById(ticket.getEventId()); Organization organization = organizationRepository.getById(Integer.valueOf(model.get("organizationId"), 10)); var ticketWithMetadata = TicketWithMetadataAttributes.build(ticket, ticketRepository.getTicketMetadata(ticket.getId())); - TemplateProcessor.renderPDFTicket(LocaleUtil.forLanguageTag(ticket.getUserLanguage()), event, reservation, + var locale = LocaleUtil.forLanguageTag(ticket.getUserLanguage()); + TemplateProcessor.renderPDFTicket(locale, event, reservation, ticketWithMetadata, ticketCategory, organization, templateManager, fileUploadManager, - configurationManager.getShortReservationID(event, reservation), baos, retrieveFieldValues, extensionManager); + configurationManager.getShortReservationID(event, reservation), baos, retrieveFieldValues, extensionManager, + TemplateProcessor.getSubscriptionDetailsModelForTicket(ticket, subscriptionRepository::findDescriptorBySubscriptionId, locale)); } catch (IOException e) { - log.warn("was not able to generate ticket pdf for ticket with id" + ticket.getId(), e); + log.warn("was not able to generate ticket pdf for ticket with id {}", ticket.getId(), e); } return baos.toByteArray(); }; @@ -242,7 +250,7 @@ private static Function<Map<String, String>, byte[]> receiptOrInvoiceFactory(Pur reservationEmailModel.put("event", purchaseContext); if(receipt.isEmpty()) { - log.warn("was not able to generate the receipt for reservation id " + reservationId + " for locale " + language); + log.warn("was not able to generate the receipt for reservation id {} for locale {}", reservationId, language); } return receipt.orElse(null); }; @@ -258,6 +266,8 @@ public void sendTicketByEmail(Ticket ticket, Organization organization = organizationRepository.getById(event.getOrganizationId()); + boolean htmlEmailEnabled = configurationManager.getFor(ConfigurationKeys.ENABLE_HTML_EMAILS, event.getConfigurationLevel()) + .getValueAsBooleanOrDefault(); // pre-generate template in order to reuse model var renderedTemplate = textBuilder.generate(ticket); @@ -266,15 +276,15 @@ public void sendTicketByEmail(Ticket ticket, var attachmentModel = new HashMap<String, String>(); // attachment model expects non-string properties to be JSON, so we convert them renderedTemplate.getSrcModel().forEach((k, v) -> { - if(v instanceof String) { - attachmentModel.put(k, (String) v); + if(v instanceof String s) { + attachmentModel.put(k, s); } else { attachmentModel.put(k, Json.toJson(v)); } }); attachments.add(CustomMessageManager.generateCalendarAttachmentForOnlineEvent(attachmentModel)); } else { - attachments.add(CustomMessageManager.generateTicketAttachment(ticket, reservation, ticketCategory, organization)); + attachments.add(CustomMessageManager.generateTicketAttachment(ticket, reservation, ticketCategory, organization, htmlEmailEnabled)); } String displayName = event.getDisplayName(); @@ -303,7 +313,7 @@ public List<String> getCCForEventOrganizer(PurchaseContext purchaseContext) { .filter(Objects::nonNull) .map(String::trim) .filter(StringUtils::isNotBlank) - .collect(Collectors.toList()); + .toList(); } public void sendSimpleEmail(PurchaseContext event, String reservationId, String recipient, String subject, TemplateGenerator textBuilder) { @@ -427,8 +437,7 @@ private Mailer.Attachment[] decodeAttachments(String input) { Set<Mailer.AttachmentIdentifier> alreadyPresents = Arrays.stream(attachments).map(Mailer.Attachment::getIdentifier).filter(Objects::nonNull).collect(Collectors.toSet()); // List<Mailer.Attachment> toReinterpret = Arrays.stream(attachments) - .filter(attachment -> attachment.getIdentifier() != null && !attachment.getIdentifier().reinterpretAs().isEmpty()) - .collect(Collectors.toList()); + .filter(attachment -> attachment.getIdentifier() != null && !attachment.getIdentifier().reinterpretAs().isEmpty()).toList(); List<Mailer.Attachment> generated = Arrays.stream(attachments) .map(attachment -> this.transformAttachment(attachment, attachment.getIdentifier())) @@ -443,7 +452,7 @@ private Mailer.Attachment[] decodeAttachments(String input) { ) ); - generated.addAll(reinterpreted.stream().filter(Objects::nonNull).collect(Collectors.toList())); + generated.addAll(reinterpreted.stream().filter(Objects::nonNull).toList()); return generated.toArray(new Mailer.Attachment[0]); } @@ -501,6 +510,40 @@ public Mailer.Attachment deserialize(JsonElement json, Type typeOfT, JsonDeseria } } + private static Function<Map<String, String>, byte[]> generateSubscriptionPDF(OrganizationRepository organizationRepository, + ConfigurationManager configurationManager, + FileUploadManager fileUploadManager, + TemplateManager templateManager, + TicketReservationRepository ticketReservationRepository, + ExtensionManager extensionManager, + SubscriptionRepository subscriptionRepository) { + return model -> { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + var subscriptionId = UUID.fromString(model.get("subscriptionId")); + var subscription = subscriptionRepository.findSubscriptionById(subscriptionId); + try { + var subscriptionDescriptor = subscriptionRepository.findDescriptorBySubscriptionId(subscriptionId); + var reservation = ticketReservationRepository.findReservationById(subscription.getReservationId()); + Organization organization = organizationRepository.getById(subscriptionDescriptor.getOrganizationId()); + var metadata = Objects.requireNonNullElseGet(subscriptionRepository.getSubscriptionMetadata(subscription.getId()), SubscriptionMetadata::empty); + TemplateProcessor.renderSubscriptionPDF(subscription, + LocaleUtil.forLanguageTag(reservation.getUserLanguage()), + subscriptionDescriptor, + reservation, + metadata, + organization, + templateManager, + fileUploadManager, + configurationManager.getShortReservationID(subscriptionDescriptor, reservation), + baos, + extensionManager); + } catch (IOException e) { + log.warn("was not able to generate subscription pdf for " + subscription.getId(), e); + } + return baos.toByteArray(); + }; + } + private static String purchaseContextCacheKey(EmailMessage message) { return message.getPurchaseContextType() + "//" + requireNonNullElse(message.getEventId(), message.getSubscriptionDescriptorId()); diff --git a/src/main/java/alfio/manager/OrganizationDeleter.java b/src/main/java/alfio/manager/OrganizationDeleter.java new file mode 100644 index 0000000000..7207203744 --- /dev/null +++ b/src/main/java/alfio/manager/OrganizationDeleter.java @@ -0,0 +1,75 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager; + +import alfio.repository.*; +import alfio.repository.user.OrganizationRepository; +import alfio.repository.user.join.UserOrganizationRepository; +import alfio.util.RequestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.security.Principal; +import java.util.List; + +@Component +@Transactional +public class OrganizationDeleter { + + private static final Logger log = LoggerFactory.getLogger(OrganizationDeleter.class); + + private final UserOrganizationRepository userOrganizationRepository; + private final OrganizationRepository organizationRepository; + private final EventRepository eventRepository; + private final EventDeleterRepository eventDeleterRepository; + private final OrganizationDeleterRepository organizationDeleterRepository; + + public OrganizationDeleter(UserOrganizationRepository userOrganizationRepository, + OrganizationRepository organizationRepository, + EventRepository eventRepository, + EventDeleterRepository eventDeleterRepository, + OrganizationDeleterRepository organizationDeleterRepository) { + this.userOrganizationRepository = userOrganizationRepository; + this.organizationRepository = organizationRepository; + this.eventRepository = eventRepository; + this.eventDeleterRepository = eventDeleterRepository; + this.organizationDeleterRepository = organizationDeleterRepository; + } + + + public boolean deleteOrganization(int organizationId, Principal principal) { + boolean isAdmin = RequestUtils.isAdmin(principal) || RequestUtils.isSystemApiKey(principal); + if (isAdmin) { + var originalOrg = organizationRepository.getById(organizationId); + log.warn("Delete organization {} ({}) initiated by user {}", organizationId, originalOrg.getName(), principal.getName()); + var disabledEventIds = eventRepository.disableEventsForOrganization(organizationId); + if (!disabledEventIds.isEmpty()) { + log.warn("deleting {} events linked to organization {}", disabledEventIds.size(), organizationId); + disabledEventIds.forEach(eventDeleterRepository::deleteAllForEvent); + } + int users = userOrganizationRepository.cleanupOrganization(organizationId); + log.warn("removed {} user(s) from organization {}", users, organizationId); + organizationDeleterRepository.deleteEmptyOrganizations(List.of(organizationId)); + return true; + } + return false; + } + + +} diff --git a/src/main/java/alfio/manager/PassKitManager.java b/src/main/java/alfio/manager/PassKitManager.java index 676ccf3145..3eac1170d9 100644 --- a/src/main/java/alfio/manager/PassKitManager.java +++ b/src/main/java/alfio/manager/PassKitManager.java @@ -17,6 +17,7 @@ package alfio.manager; import alfio.manager.system.ConfigurationManager; +import alfio.manager.system.Mailer; import alfio.model.*; import alfio.model.system.ConfigurationKeys; import alfio.model.user.Organization; @@ -24,6 +25,7 @@ import alfio.repository.user.OrganizationRepository; import alfio.util.Json; import alfio.util.LocaleUtil; +import alfio.util.MustacheCustomTag; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.ryantenney.passkit4j.Pass; @@ -33,10 +35,12 @@ import com.ryantenney.passkit4j.sign.PassSigner; import com.ryantenney.passkit4j.sign.PassSignerImpl; import com.ryantenney.passkit4j.sign.PassSigningException; -import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.tuple.Pair; import org.imgscalr.Scalr; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; @@ -52,10 +56,10 @@ import static alfio.model.system.ConfigurationKeys.*; @Component -@AllArgsConstructor -@Log4j2 public class PassKitManager { + private static final Logger log = LoggerFactory.getLogger(PassKitManager.class); + private static final String APPLE_PASS = "ApplePass"; private final Cache<String, Optional<byte[]>> passKitLogoCache = Caffeine.newBuilder() .maximumSize(20) @@ -68,7 +72,22 @@ public class PassKitManager { private final EventDescriptionRepository eventDescriptionRepository; private final TicketCategoryRepository ticketCategoryRepository; private final TicketRepository ticketRepository; - private final TicketReservationRepository ticketReservationRepository; + + public PassKitManager(EventRepository eventRepository, + OrganizationRepository organizationRepository, + ConfigurationManager configurationManager, + FileUploadManager fileUploadManager, + EventDescriptionRepository eventDescriptionRepository, + TicketCategoryRepository ticketCategoryRepository, + TicketRepository ticketRepository) { + this.eventRepository = eventRepository; + this.organizationRepository = organizationRepository; + this.configurationManager = configurationManager; + this.fileUploadManager = fileUploadManager; + this.eventDescriptionRepository = eventDescriptionRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketRepository = ticketRepository; + } public boolean writePass(Ticket ticket, EventAndOrganizationId event, OutputStream out) throws IOException, PassSigningException { @@ -85,6 +104,10 @@ public boolean writePass(Ticket ticket, EventAndOrganizationId event, OutputStre byte[] getPass(Map<String, String> model) { try { + if (BooleanUtils.TRUE.equals(model.get(Mailer.SKIP_PASSBOOK))) { + log.trace("HTML email enabled. Skipping passbook generation"); + return null; + } Ticket ticket = Json.fromJson(model.get("ticket"), Ticket.class); int eventId = ticket.getEventId(); Event event = eventRepository.findById(eventId); @@ -150,7 +173,8 @@ private void buildPass(Ticket ticket, String privateKeyAlias = config.get(PASSBOOK_PRIVATE_KEY_ALIAS); - String eventDescription = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(event.getId(), EventDescription.EventDescriptionType.DESCRIPTION, ticket.getUserLanguage()).orElse(""); + String eventDescription = MustacheCustomTag.renderToTextCommonmark( + eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(event.getId(), EventDescription.EventDescriptionType.DESCRIPTION, ticket.getUserLanguage()).orElse("")); TicketCategory category = ticketCategoryRepository.getById(ticket.getCategoryId()); var ticketValidityStart = Optional.ofNullable(category.getTicketValidityStart(event.getZoneId())).orElse(event.getBegin()); Pass pass = new Pass() @@ -165,7 +189,7 @@ private void buildPass(Ticket ticket, .relevantDate(Date.from(ticketValidityStart.toInstant())) .expirationDate(Date.from(Optional.ofNullable(category.getTicketValidityEnd(event.getZoneId())).orElse(event.getEnd()).toInstant())) - .barcode(new Barcode(BarcodeFormat.QR, ticket.ticketCode(event.getPrivateKey()))) + .barcode(new Barcode(BarcodeFormat.QR, ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()))) .labelColor(Color.BLACK) .foregroundColor(Color.BLACK) .backgroundColor(Color.WHITE) @@ -213,7 +237,7 @@ private void buildPass(Ticket ticket, }); pass.files(passResources.toArray(new PassResource[0])); - try(InputStream appleCert = new ClassPathResource("/alfio/certificates/AppleWWDRCA.cer").getInputStream()) { + try(InputStream appleCert = new ClassPathResource("/alfio/certificates/AppleWWDRCAG4.cer").getInputStream()) { PassSigner signer = PassSignerImpl.builder() .keystore(new ByteArrayInputStream(keystoreRaw), keystorePwd) .alias(privateKeyAlias) @@ -228,6 +252,14 @@ private String buildAuthenticationToken(Ticket ticket, EventAndOrganizationId ev return Ticket.hmacSHA256Base64(privateKey, code); } + public Optional<Pair<EventAndOrganizationId, Ticket>> retrieveTicketDetails(String eventName, String ticketUuid) { + return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) + .flatMap(e -> ticketRepository.findOptionalByUUID(ticketUuid) + .filter(t -> e.getId() == t.getEventId()) + .map(t -> Pair.of(e, t)) + ); + } + public Optional<Pair<EventAndOrganizationId, Ticket>> validateToken(String eventName, String typeIdentifier, String ticketUuid, String authorizationHeader) { String token; if(authorizationHeader.startsWith(APPLE_PASS)) { diff --git a/src/main/java/alfio/manager/PaymentManager.java b/src/main/java/alfio/manager/PaymentManager.java index 7815460d75..fb0a0d6bf1 100644 --- a/src/main/java/alfio/manager/PaymentManager.java +++ b/src/main/java/alfio/manager/PaymentManager.java @@ -29,24 +29,28 @@ import alfio.repository.AuditingRepository; import alfio.repository.TransactionRepository; import alfio.repository.user.UserRepository; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import java.security.Principal; +import java.time.ZonedDateTime; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Objects.requireNonNullElse; + @Component -@Log4j2 -@AllArgsConstructor public class PaymentManager { public static final String PAYMENT_TOKEN = "PAYMENT_TOKEN"; + private static final Logger log = LoggerFactory.getLogger(PaymentManager.class); + private final TransactionRepository transactionRepository; private final ConfigurationManager configurationManager; private final AuditingRepository auditingRepository; @@ -55,6 +59,20 @@ public class PaymentManager { private final List<PaymentProvider> paymentProviders; // injected by Spring + public PaymentManager(TransactionRepository transactionRepository, + ConfigurationManager configurationManager, + AuditingRepository auditingRepository, + UserRepository userRepository, + ExtensionManager extensionManager, + List<PaymentProvider> paymentProviders) { + this.transactionRepository = transactionRepository; + this.configurationManager = configurationManager; + this.auditingRepository = auditingRepository; + this.userRepository = userRepository; + this.extensionManager = extensionManager; + this.paymentProviders = paymentProviders; + } + public Optional<PaymentProvider> lookupProviderByTransactionAndCapabilities(Transaction transaction, List<Class<? extends Capability>> capabilities) { return paymentProviders.stream() .filter(filterByCapabilities(capabilities)) @@ -85,22 +103,27 @@ List<Map.Entry<PaymentMethod, Set<PaymentProxy>>> validateSelection(List<Payment var paymentContext = new PaymentContext(null, ConfigurationLevel.organization(organizationId)); Map<PaymentMethod, Set<PaymentProxy>> proxiesByMethod = paymentProxies.stream() - .flatMap(proxy -> streamActiveProvidersByProxy(proxy, paymentContext)) + .flatMap(proxy -> streamProvidersByProxyAndCapabilities(proxy, List.of())) .map(provider -> Pair.of(provider.getPaymentProxy(), provider.getSupportedPaymentMethods(paymentContext, TransactionRequest.empty()))) .flatMap(pair -> pair.getValue().stream().map(pm -> Pair.of(pm, pair.getKey()))) // flip .collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toSet()))); return proxiesByMethod.entrySet().stream() .filter(e -> e.getValue().size() > 1) - .collect(Collectors.toList()); + .toList(); + } + + private Stream<PaymentProvider> streamProvidersByProxyAndCapabilities(PaymentProxy paymentProxy, + List<Class<? extends Capability>> capabilities) { + return paymentProviders.stream() + .filter(pp -> pp.getPaymentProxy() == paymentProxy) + .filter(filterByCapabilities(capabilities)); } Stream<PaymentProvider> streamActiveProvidersByProxyAndCapabilities(PaymentProxy paymentProxy, PaymentContext paymentContext, List<Class<? extends Capability>> capabilities) { - return paymentProviders.stream() - .filter(pp -> pp.getPaymentProxy() == paymentProxy && pp.isActive(paymentContext)) - .filter(filterByCapabilities(capabilities)); + return streamProvidersByProxyAndCapabilities(paymentProxy, capabilities).filter((pp) -> pp.isActive(paymentContext)); } private static Predicate<PaymentProvider> filterByCapabilities(List<Class<? extends Capability>> capabilities) { @@ -119,7 +142,7 @@ private List<PaymentMethodDTO> getPaymentMethods(PaymentContext context, Transac .filter(p -> !blacklist.contains(p.getKey())) .map(proxy -> Pair.of(proxy, paymentMethodsByProxy(context, transactionRequest, proxy))) .flatMap(pair -> pair.getRight().stream().map(pm -> new PaymentMethodDTO(pair.getLeft(), pm, PaymentMethodDTO.PaymentMethodStatus.ACTIVE))) - .collect(Collectors.toList()); + .toList(); } private Set<PaymentMethod> paymentMethodsByProxy(PaymentContext context, TransactionRequest transactionRequest, PaymentProxy proxy) { @@ -164,14 +187,14 @@ Audit.EventType.REFUND_ATTEMPT_FAILED, new Date(), Audit.EntityType.RESERVATION, return res; } - TransactionAndPaymentInfo getInfo(TicketReservation reservation, PurchaseContext purchaseContext) { + public TransactionAndPaymentInfo getInfo(TicketReservation reservation, PurchaseContext purchaseContext) { Optional<TransactionAndPaymentInfo> maybeTransaction = transactionRepository.loadOptionalByReservationId(reservation.getId()) .map(transaction -> internalGetInfo(reservation, purchaseContext, transaction)); maybeTransaction.ifPresent(info -> { try { - Transaction transaction = info.getTransaction(); + Transaction transaction = info.transaction(); String transactionId = transaction.getTransactionId(); - PaymentInformation paymentInformation = info.getPaymentInformation(); + PaymentInformation paymentInformation = info.paymentInformation(); if(paymentInformation != null && feesUpdated(transaction, paymentInformation)) { transactionRepository.updateFees(transactionId, reservation.getId(), safeParseLong(paymentInformation.getPlatformFee()), safeParseLong(paymentInformation.getFee())); } @@ -182,6 +205,19 @@ TransactionAndPaymentInfo getInfo(TicketReservation reservation, PurchaseContext return maybeTransaction.orElseGet(() -> new TransactionAndPaymentInfo(reservation.getPaymentMethod(),null, new PaymentInformation(reservation.getPaidAmount(), null, null, null))); } + public void updateTransactionDetails(String reservationId, + String notes, + ZonedDateTime timestamp, + Principal principal) { + // TODO check if user can modify transaction once we have a centralized service. + var existingTransaction = transactionRepository.loadByReservationId(reservationId); + Validate.isTrue(existingTransaction.isTimestampEditable() || timestamp == null, "Cannot modify timestamp"); + var existingMetadata = new HashMap<>(existingTransaction.getMetadata()); + existingMetadata.put(Transaction.NOTES_KEY, notes); + int result = transactionRepository.updateDetailsById(existingTransaction.getId(), existingMetadata, requireNonNullElse(timestamp, existingTransaction.getTimestamp())); + Validate.isTrue(result == 1, "Expected 1, got " + result); + } + private boolean feesUpdated(Transaction transaction, PaymentInformation paymentInformation) { return transaction.getPlatformFee() != safeParseLong(paymentInformation.getPlatformFee()) || transaction.getGatewayFee() != safeParseLong(paymentInformation.getFee()); @@ -234,14 +270,11 @@ public Optional<PaymentResult> getTransactionStatus(TicketReservation reservatio return transactionRepository.loadOptionalByReservationId(reservation.getId()) .filter(transaction -> transaction.getPaymentProxy().getPaymentMethod() == paymentMethod) .map(transaction -> { - switch(transaction.getStatus()) { - case COMPLETE: - return PaymentResult.successful(transaction.getPaymentId()); - case FAILED: - return PaymentResult.failed(null); - default: - return PaymentResult.initialized(transaction.getPaymentId()); - } + return switch (transaction.getStatus()) { + case COMPLETE -> PaymentResult.successful(transaction.getPaymentId()); + case FAILED -> PaymentResult.failed(null); + default -> PaymentResult.initialized(transaction.getPaymentId()); + }; }); } @@ -269,13 +302,19 @@ public boolean removePaymentTokenReservation(String reservationId) { }).orElse(false); } - @Data + public static final class PaymentMethodDTO { public enum PaymentMethodStatus { ACTIVE, ERROR } + public PaymentMethodDTO(PaymentProxy paymentProxy, PaymentMethod paymentMethod, PaymentMethodStatus status) { + this.paymentProxy = paymentProxy; + this.paymentMethod = paymentMethod; + this.status = status; + } + private final PaymentProxy paymentProxy; private final PaymentMethod paymentMethod; @@ -285,7 +324,7 @@ public boolean isActive() { return status == PaymentMethodStatus.ACTIVE; } - @Deprecated + @Deprecated(forRemoval = true) public Set<String> getOnlyForCurrency() { return paymentProxy.getOnlyForCurrency(); } @@ -293,5 +332,13 @@ public Set<String> getOnlyForCurrency() { public PaymentMethod getPaymentMethod() { return paymentMethod; } + + public PaymentProxy getPaymentProxy() { + return paymentProxy; + } + + public PaymentMethodStatus getStatus() { + return status; + } } } diff --git a/src/main/java/alfio/manager/PollManager.java b/src/main/java/alfio/manager/PollManager.java index 3597fe393e..4956832f3b 100644 --- a/src/main/java/alfio/manager/PollManager.java +++ b/src/main/java/alfio/manager/PollManager.java @@ -26,7 +26,6 @@ import alfio.repository.*; import alfio.util.Json; import alfio.util.PinGenerator; -import lombok.AllArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -43,7 +42,6 @@ import static java.util.Objects.requireNonNullElse; @Component -@AllArgsConstructor @Transactional public class PollManager { private final PollRepository pollRepository; @@ -53,6 +51,20 @@ public class PollManager { private final TicketSearchRepository ticketSearchRepository; private final AuditingRepository auditingRepository; + public PollManager(PollRepository pollRepository, + EventRepository eventRepository, + TicketRepository ticketRepository, + NamedParameterJdbcTemplate jdbcTemplate, + TicketSearchRepository ticketSearchRepository, + AuditingRepository auditingRepository) { + this.pollRepository = pollRepository; + this.eventRepository = eventRepository; + this.ticketRepository = ticketRepository; + this.jdbcTemplate = jdbcTemplate; + this.ticketSearchRepository = ticketSearchRepository; + this.auditingRepository = auditingRepository; + } + public Result<List<Poll>> getActiveForEvent(String eventName, String pin) { return validatePinAndEvent(pin, eventName) .flatMap(eventAndTicket -> Result.success(pollRepository.findActiveForEvent(eventAndTicket.getLeft().getId()))); @@ -139,9 +151,9 @@ public Optional<PollWithOptions> updatePoll(String eventName, PollModification f .flatMap(event -> { var pollId = form.getId(); var existingPollWithOptions = getSingleForEvent(pollId, event).orElseThrow(); - var existingPoll = existingPollWithOptions.getPoll(); - var tags = existingPoll.getAllowedTags(); - if(form.isAccessRestricted() == existingPoll.getAllowedTags().isEmpty()) { + var existingPoll = existingPollWithOptions.poll(); + var tags = existingPoll.allowedTags(); + if(form.isAccessRestricted() == existingPoll.allowedTags().isEmpty()) { tags = form.isAccessRestricted() ? List.of(UUID.randomUUID().toString()) : List.of(); } Validate.isTrue(pollRepository.update(form.getTitle(), form.getDescription(), tags, form.getOrder(), pollId, event.getId()) == 1); @@ -174,7 +186,7 @@ public Optional<List<PollParticipant>> searchTicketsToAllow(String eventName, Lo Validate.isTrue(StringUtils.isNotBlank(filter)); return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) .flatMap(event -> pollRepository.findSingleForEvent(event.getId(), pollId) - .map(p -> ticketSearchRepository.filterConfirmedTicketsInEventForPoll(event.getId(), 20, "%"+filter+"%", p.getAllowedTags()))); + .map(p -> ticketSearchRepository.filterConfirmedTicketsInEventForPoll(event.getId(), 20, "%"+filter+"%", p.allowedTags()))); } public Optional<Boolean> allowTicketsToVote(String eventName, List<Integer> ids, long pollId) { @@ -182,8 +194,8 @@ public Optional<Boolean> allowTicketsToVote(String eventName, List<Integer> ids, return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) .map(event -> { var poll = pollRepository.findSingleForEvent(event.getId(), pollId).orElseThrow(); - Validate.isTrue(CollectionUtils.isNotEmpty(poll.getAllowedTags())); - var tag = poll.getAllowedTags().get(0); + Validate.isTrue(CollectionUtils.isNotEmpty(poll.allowedTags())); + var tag = poll.allowedTags().get(0); var result = ticketRepository.tagTickets(ids, event.getId(), tag); Validate.isTrue(ids.size() == result, "Unable to tag tickets"); var auditingResults = auditingRepository.registerTicketTag(ids, List.of(Map.of("tag", tag))); @@ -196,13 +208,13 @@ public Optional<List<PollParticipant>> removeParticipants(String eventName, List return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) .map(event -> { var poll = pollRepository.findSingleForEvent(event.getId(), pollId).orElseThrow(); - Validate.isTrue(CollectionUtils.isNotEmpty(poll.getAllowedTags())); - var tag = poll.getAllowedTags().get(0); + Validate.isTrue(CollectionUtils.isNotEmpty(poll.allowedTags())); + var tag = poll.allowedTags().get(0); var result = ticketRepository.untagTickets(ticketIds, event.getId(), tag); Validate.isTrue(result == 1, "Error while removing tag"); var auditingResults = auditingRepository.registerTicketUntag(ticketIds, List.of(Map.of("tag", tag))); Validate.isTrue(auditingResults == ticketIds.size(), "Error while writing auditing"); - return ticketRepository.getTicketsForEventByTags(event.getId(), poll.getAllowedTags()); + return ticketRepository.getTicketsForEventByTags(event.getId(), poll.allowedTags()); }); } @@ -211,7 +223,7 @@ public Optional<PollWithOptions> removeOption(String eventName, Long pollId, Lon .flatMap(event -> { var poll = pollRepository.findSingleForEvent(event.getId(), pollId).orElseThrow(); Validate.isTrue(pollRepository.deleteOption(pollId, optionId) == 1, "Error while deleting option"); - return getSingleForEvent(poll.getId(), event); + return getSingleForEvent(poll.id(), event); }); } @@ -219,7 +231,7 @@ public Optional<List<PollParticipant>> fetchAllowedTickets(String eventName, lon return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) .map(event -> { var poll = pollRepository.findSingleForEvent(event.getId(), pollId).orElseThrow(); - return ticketRepository.getTicketsForEventByTags(event.getId(), poll.getAllowedTags()); + return ticketRepository.getTicketsForEventByTags(event.getId(), poll.allowedTags()); }); } @@ -228,13 +240,13 @@ public Optional<PollStatistics> getStatisticsFor(String eventName, long pollId) .flatMap(event -> pollRepository.findSingleForEvent(event.getId(), pollId) .map(p -> { int allowedParticipants; - if(p.getAllowedTags().isEmpty()) { + if(p.allowedTags().isEmpty()) { allowedParticipants = eventRepository.findStatisticsFor(event.getId()).getCheckedInTickets(); } else { - allowedParticipants = ticketRepository.countTicketsMatchingTagsAndStatus(event.getId(), p.getAllowedTags(), List.of(Ticket.TicketStatus.CHECKED_IN.name())); + allowedParticipants = ticketRepository.countTicketsMatchingTagsAndStatus(event.getId(), p.allowedTags(), List.of(Ticket.TicketStatus.CHECKED_IN.name())); } - var statistics = pollRepository.getStatisticsFor(p.getId(), event.getId()); - return new PollStatistics(statistics.stream().mapToInt(PollOptionStatistics::getVotes).sum(), allowedParticipants, statistics); + var statistics = pollRepository.getStatisticsFor(p.id(), event.getId()); + return new PollStatistics(statistics.stream().mapToInt(PollOptionStatistics::votes).sum(), allowedParticipants, statistics); })); } diff --git a/src/main/java/alfio/manager/PromoCodeRequestManager.java b/src/main/java/alfio/manager/PromoCodeRequestManager.java index 3253f8f744..1598f128c7 100644 --- a/src/main/java/alfio/manager/PromoCodeRequestManager.java +++ b/src/main/java/alfio/manager/PromoCodeRequestManager.java @@ -20,6 +20,7 @@ import alfio.manager.support.response.ValidatedResponse; import alfio.model.Event; import alfio.model.PromoCodeDiscount; +import alfio.model.PromoCodeUsageResult; import alfio.model.SpecialPrice; import alfio.model.modification.TicketReservationModification; import alfio.model.result.ValidationResult; @@ -31,7 +32,6 @@ import alfio.util.ErrorsCode; import alfio.util.RequestUtils; import alfio.util.ReservationUtil; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -44,6 +44,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.function.BiConsumer; @@ -52,7 +53,6 @@ import static alfio.model.PromoCodeDiscount.categoriesOrNull; @Component -@AllArgsConstructor public class PromoCodeRequestManager { private final SpecialPriceRepository specialPriceRepository; @@ -63,6 +63,22 @@ public class PromoCodeRequestManager { private final TicketReservationManager ticketReservationManager; private final ClockProvider clockProvider; + public PromoCodeRequestManager(SpecialPriceRepository specialPriceRepository, + PromoCodeDiscountRepository promoCodeRepository, + TicketCategoryRepository ticketCategoryRepository, + EventManager eventManager, + EventRepository eventRepository, + TicketReservationManager ticketReservationManager, + ClockProvider clockProvider) { + this.specialPriceRepository = specialPriceRepository; + this.promoCodeRepository = promoCodeRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.eventManager = eventManager; + this.eventRepository = eventRepository; + this.ticketReservationManager = ticketReservationManager; + this.clockProvider = clockProvider; + } + enum PromoCodeType { SPECIAL_PRICE, PROMO_CODE_DISCOUNT, TICKET_CATEGORY_CODE, NOT_FOUND } @@ -136,7 +152,7 @@ public ValidatedResponse<Pair<Optional<SpecialPrice>, Optional<PromoCodeDiscount ZonedDateTime now = ZonedDateTime.now(clockProvider.withZone(eventZoneId)); Optional<String> maybeSpecialCode = Optional.ofNullable(StringUtils.trimToNull(promoCode)); Optional<SpecialPrice> specialCode = maybeSpecialCode.flatMap(specialPriceRepository::getByCode); - Optional<PromoCodeDiscount> promotionCodeDiscount = maybeSpecialCode.flatMap((trimmedCode) -> promoCodeRepository.findPublicPromoCodeInEventOrOrganization(event.getId(), trimmedCode)); + Optional<PromoCodeDiscount> promotionCodeDiscount = maybeSpecialCode.flatMap(trimmedCode -> promoCodeRepository.findPublicPromoCodeInEventOrOrganization(event.getId(), trimmedCode)); var result = Pair.of(specialCode, promotionCodeDiscount); @@ -152,15 +168,16 @@ public ValidatedResponse<Pair<Optional<SpecialPrice>, Optional<PromoCodeDiscount return errorResponse; } - } else if (promotionCodeDiscount.isPresent() && !promotionCodeDiscount.get().isCurrentlyValid(eventZoneId, now)) { - return errorResponse; - } else if (promotionCodeDiscount.isPresent() && isDiscountCodeUsageExceeded(promotionCodeDiscount.get())){ - return errorResponse; - } else if(promotionCodeDiscount.isEmpty()) { + } else if(promotionCodeDiscount.isPresent()) { + var pcd = promotionCodeDiscount.get(); + if (!pcd.isCurrentlyValid(eventZoneId, now) + || isDiscountCodeUsageExceeded(pcd) + || (pcd.hasCurrencyCode() && !pcd.getCurrencyCode().equals(event.getCurrency()))) { + return errorResponse; + } + } else { return errorResponse; } - // - return new ValidatedResponse<>(ValidationResult.success(), result); } @@ -211,4 +228,26 @@ private Optional<String> createTicketReservation(ReservationForm reservation, .flatMap(selected -> ticketReservationManager.createTicketReservation(event, selected.getLeft(), selected.getRight(), promoCodeDiscount, locale, bindingResult, principal)); } + public Optional<PromoCodeDiscount> findById(int id) { + return promoCodeRepository.findOptionalById(id); + } + + public void disablePromoCode(int promoCodeId) { + promoCodeRepository.updateEventPromoCodeEnd(promoCodeId, ZonedDateTime.now(clockProvider.getClock())); + } + + public int countUsage(int promoCodeId) { + Optional<PromoCodeDiscount> code = findById(promoCodeId); + if(code.isEmpty()) { + return 0; + } + return promoCodeRepository.countConfirmedPromoCode(promoCodeId, categoriesOrNull(code.get()), null, categoriesOrNull(code.get()) != null ? "X" : null); + } + + public List<PromoCodeUsageResult> retrieveDetailedUsage(int promoCodeId, Integer eventId) { + return findById(promoCodeId) + .map(pc -> promoCodeRepository.findDetailedUsage(pc.getPromoCode(), eventId)) + .orElse(List.of()); + } + } diff --git a/src/main/java/alfio/manager/PurchaseContextManager.java b/src/main/java/alfio/manager/PurchaseContextManager.java index e57f16639c..0a4d8112de 100644 --- a/src/main/java/alfio/manager/PurchaseContextManager.java +++ b/src/main/java/alfio/manager/PurchaseContextManager.java @@ -22,46 +22,60 @@ import alfio.repository.EventRepository; import alfio.repository.SubscriptionRepository; import alfio.repository.TicketReservationRepository; -import lombok.extern.log4j.Log4j2; +import alfio.repository.user.OrganizationRepository; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.security.Principal; +import java.util.Objects; import java.util.Optional; import java.util.UUID; @Transactional(readOnly = true) @Component -@Log4j2 public class PurchaseContextManager { + private static final Logger log = LoggerFactory.getLogger(PurchaseContextManager.class); + private final EventRepository eventRepository; private final SubscriptionRepository subscriptionRepository; private final TicketReservationRepository ticketReservationRepository; + private final OrganizationRepository organizationRepository; public PurchaseContextManager(EventRepository eventRepository, SubscriptionRepository subscriptionRepository, - TicketReservationRepository ticketReservationRepository) { + TicketReservationRepository ticketReservationRepository, + OrganizationRepository organizationRepository) { this.eventRepository = eventRepository; this.subscriptionRepository = subscriptionRepository; this.ticketReservationRepository = ticketReservationRepository; + this.organizationRepository = organizationRepository; } public Optional<? extends PurchaseContext> findBy(PurchaseContext.PurchaseContextType purchaseContextType, String publicIdentifier) { - switch (purchaseContextType) { - case event: return eventRepository.findOptionalByShortName(publicIdentifier); - case subscription: return subscriptionRepository.findOne(UUID.fromString(publicIdentifier)); - default: throw new IllegalStateException("not a covered type " + purchaseContextType); - } + return switch (purchaseContextType) { + case event -> eventRepository.findOptionalByShortName(publicIdentifier); + case subscription -> subscriptionRepository.findOne(UUID.fromString(publicIdentifier)); + default -> throw new IllegalStateException("not a covered type " + purchaseContextType); + }; + } + + // temporary until we have a proper ownership check service (WIP) + public boolean validateAccess(PurchaseContext purchaseContext, Principal principal) { + var username = Objects.requireNonNull(principal).getName(); + return organizationRepository.findOrganizationForUser(username, purchaseContext.getOrganizationId()).isPresent(); } Optional<? extends PurchaseContext> findById(PurchaseContext.PurchaseContextType purchaseContextType, String idAsString) { - switch (purchaseContextType) { - case event: return eventRepository.findOptionalById(Integer.parseInt(idAsString)); - case subscription: return subscriptionRepository.findOne(UUID.fromString(idAsString)); - default: throw new IllegalStateException("not a covered type " + purchaseContextType); - } + return switch (purchaseContextType) { + case event -> eventRepository.findOptionalById(Integer.parseInt(idAsString)); + case subscription -> subscriptionRepository.findOne(UUID.fromString(idAsString)); + default -> throw new IllegalStateException("not a covered type " + purchaseContextType); + }; } public Optional<PurchaseContext> findByReservationId(String reservationId) { diff --git a/src/main/java/alfio/manager/PurchaseContextSearchManager.java b/src/main/java/alfio/manager/PurchaseContextSearchManager.java index 52432d6e69..f8ca3285a2 100644 --- a/src/main/java/alfio/manager/PurchaseContextSearchManager.java +++ b/src/main/java/alfio/manager/PurchaseContextSearchManager.java @@ -18,41 +18,80 @@ import alfio.model.Event; import alfio.model.PurchaseContext; +import alfio.model.ReservationPaymentDetail; import alfio.model.TicketReservation; import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.transaction.PaymentProxy; import alfio.repository.TicketSearchRepository; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; @Component @Transactional(readOnly = true) -@AllArgsConstructor public class PurchaseContextSearchManager { + private static final int PAGE_SIZE = 50; + private static final List<String> SUPPORTED_PAYMENT_METHODS = EnumSet.complementOf(EnumSet.of(PaymentProxy.NONE, PaymentProxy.ADMIN)) + .stream() + .map(PaymentProxy::name) + .collect(Collectors.toUnmodifiableList()); private final TicketSearchRepository ticketSearchRepository; + public PurchaseContextSearchManager(TicketSearchRepository ticketSearchRepository) { + this.ticketSearchRepository = ticketSearchRepository; + } + public Pair<List<TicketReservation>, Integer> findAllReservationsFor(PurchaseContext purchaseContext, Integer page, String search, List<TicketReservation.TicketReservationStatus> status) { - final int pageSize = 50; - int offset = page == null ? 0 : page * pageSize; + int offset = page == null ? 0 : page * PAGE_SIZE; String toSearch = StringUtils.trimToNull(search); toSearch = toSearch == null ? null : ("%" + toSearch + "%"); List<String> toFilter = (status == null || status.isEmpty() ? Arrays.asList(TicketReservation.TicketReservationStatus.values()) : status).stream().map(TicketReservation.TicketReservationStatus::toString).collect(toList()); if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { var event = (Event)purchaseContext; - List<TicketReservation> reservationsForEvent = ticketSearchRepository.findReservationsForEvent(event.getId(), offset, pageSize, toSearch, toFilter); + List<TicketReservation> reservationsForEvent = ticketSearchRepository.findReservationsForEvent(event.getId(), offset, PAGE_SIZE, toSearch, toFilter); return Pair.of(reservationsForEvent, ticketSearchRepository.countReservationsForEvent(event.getId(), toSearch, toFilter)); } else { var subscription = (SubscriptionDescriptor) purchaseContext; - List<TicketReservation> reservationsForSubscription = ticketSearchRepository.findReservationsForSubscription(subscription.getId(), offset, pageSize, toSearch, toFilter); + List<TicketReservation> reservationsForSubscription = ticketSearchRepository.findReservationsForSubscription(subscription.getId(), offset, PAGE_SIZE, toSearch, toFilter); return Pair.of(reservationsForSubscription, ticketSearchRepository.countReservationsForSubscription(subscription.getId(), toSearch, toFilter)); } } + + public Pair<List<ReservationPaymentDetail>, Integer> findAllPaymentsFor(PurchaseContext purchaseContext, Integer page, String search) { + int offset = page == null ? 0 : page * PAGE_SIZE; + String toSearch = StringUtils.trimToNull(search); + toSearch = toSearch == null ? null : ("%" + toSearch + "%"); + var toFilter = List.of(TicketReservation.TicketReservationStatus.COMPLETE.name()); + + if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { + var event = (Event)purchaseContext; + List<ReservationPaymentDetail> reservationsForEvent = ticketSearchRepository.findAllPaymentsForEvent(event.getId(), offset, PAGE_SIZE, toSearch, toFilter, SUPPORTED_PAYMENT_METHODS); + return Pair.of(reservationsForEvent, ticketSearchRepository.countConfirmedPaymentsForEvent(event.getId(), toSearch, toFilter, SUPPORTED_PAYMENT_METHODS)); + } else { + // functionality is not yet available for subscriptions + throw new UnsupportedOperationException("not implemented"); + } + } + + public List<ReservationPaymentDetail> findAllPaymentsForExport(PurchaseContext purchaseContext, String search) { + String toSearch = StringUtils.trimToNull(search); + toSearch = toSearch == null ? null : ("%" + toSearch + "%"); + var toFilter = List.of(TicketReservation.TicketReservationStatus.COMPLETE.name()); + if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { + var event = (Event)purchaseContext; + return ticketSearchRepository.findAllEventPaymentsForExport(event.getId(), toSearch, toFilter, SUPPORTED_PAYMENT_METHODS); + } else { + // functionality is not yet available for subscriptions + throw new UnsupportedOperationException("not implemented"); + } + } } diff --git a/src/main/java/alfio/manager/RecaptchaService.java b/src/main/java/alfio/manager/RecaptchaService.java index 267a80f45a..3b00389749 100644 --- a/src/main/java/alfio/manager/RecaptchaService.java +++ b/src/main/java/alfio/manager/RecaptchaService.java @@ -20,8 +20,6 @@ import alfio.model.system.ConfigurationKeys; import alfio.util.HttpUtils; import alfio.util.Json; -import lombok.AllArgsConstructor; -import lombok.Data; import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Component; @@ -32,12 +30,16 @@ import java.util.Map; @Component -@AllArgsConstructor public class RecaptchaService { private final HttpClient client; private final ConfigurationManager configurationManager; + public RecaptchaService(HttpClient client, ConfigurationManager configurationManager) { + this.client = client; + this.configurationManager = configurationManager; + } + public boolean checkRecaptcha(String recaptchaResponse, HttpServletRequest req) { return configurationManager.getForSystem(ConfigurationKeys.RECAPTCHA_SECRET).getValue() @@ -63,8 +65,15 @@ private static boolean recaptchaRequest(HttpClient client, String secret, String } } - @Data public static class RecatpchaResponse { private boolean success; + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } } } diff --git a/src/main/java/alfio/manager/ReservationFinalizer.java b/src/main/java/alfio/manager/ReservationFinalizer.java new file mode 100644 index 0000000000..ae80e85d95 --- /dev/null +++ b/src/main/java/alfio/manager/ReservationFinalizer.java @@ -0,0 +1,488 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager; + +import alfio.manager.payment.PaymentSpecification; +import alfio.manager.support.FeeCalculator; +import alfio.manager.support.IncompatibleStateException; +import alfio.manager.support.RetryFinalizeReservation; +import alfio.manager.support.reservation.OrderSummaryGenerator; +import alfio.manager.support.reservation.ReservationAuditingHelper; +import alfio.manager.support.reservation.ReservationCostCalculator; +import alfio.manager.support.reservation.ReservationEmailContentHelper; +import alfio.manager.system.AdminJobManager; +import alfio.manager.system.ConfigurationLevel; +import alfio.manager.system.ConfigurationManager; +import alfio.model.*; +import alfio.model.metadata.TicketMetadata; +import alfio.model.metadata.TicketMetadataContainer; +import alfio.model.modification.TransactionMetadataModification; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.support.UserIdAndOrganizationId; +import alfio.model.system.command.FinalizeReservation; +import alfio.model.transaction.PaymentProxy; +import alfio.model.transaction.Transaction; +import alfio.repository.*; +import alfio.repository.system.AdminJobQueueRepository; +import alfio.repository.user.UserRepository; +import alfio.util.ClockProvider; +import alfio.util.Json; +import alfio.util.LocaleUtil; +import alfio.util.ReservationUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import java.time.ZonedDateTime; +import java.time.temporal.ChronoField; +import java.util.*; +import java.util.function.Function; + +import static alfio.manager.system.AdminJobExecutor.JobName.RETRY_RESERVATION_CONFIRMATION; +import static alfio.model.Audit.EventType.SUBSCRIPTION_ACQUIRED; +import static alfio.model.TicketReservation.TicketReservationStatus.*; +import static alfio.model.system.ConfigurationKeys.*; +import static alfio.util.ReservationUtil.hasPrivacyPolicy; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNullElse; +import static java.util.stream.Collectors.toMap; + +@Component +public class ReservationFinalizer { + private static final Logger log = LoggerFactory.getLogger(ReservationFinalizer.class); + private final TransactionTemplate transactionTemplate; + private final TicketReservationRepository ticketReservationRepository; + private final UserRepository userRepository; + private final ExtensionManager extensionManager; + private final AuditingRepository auditingRepository; + private final ClockProvider clockProvider; + private final ConfigurationManager configurationManager; + private final SubscriptionRepository subscriptionRepository; + private final ReservationAuditingHelper auditingHelper; + private final TicketRepository ticketRepository; + private final ReservationEmailContentHelper reservationOperationHelper; + private final SpecialPriceRepository specialPriceRepository; + private final WaitingQueueManager waitingQueueManager; + private final TicketCategoryRepository ticketCategoryRepository; + private final ReservationCostCalculator reservationCostCalculator; + private final BillingDocumentManager billingDocumentManager; + private final AdditionalServiceItemRepository additionalServiceItemRepository; + private final OrderSummaryGenerator orderSummaryGenerator; + private final ReservationEmailContentHelper reservationHelper; + private final TransactionRepository transactionRepository; + private final AdminJobQueueRepository adminJobQueueRepository; + private final PurchaseContextManager purchaseContextManager; + private final Json json; + + + public ReservationFinalizer(PlatformTransactionManager transactionManager, + TicketReservationRepository ticketReservationRepository, + UserRepository userRepository, + ExtensionManager extensionManager, + AuditingRepository auditingRepository, + ClockProvider clockProvider, + ConfigurationManager configurationManager, + SubscriptionRepository subscriptionRepository, + TicketRepository ticketRepository, + ReservationEmailContentHelper reservationEmailContentHelper, + SpecialPriceRepository specialPriceRepository, + WaitingQueueManager waitingQueueManager, + TicketCategoryRepository ticketCategoryRepository, + ReservationCostCalculator reservationCostCalculator, + BillingDocumentManager billingDocumentManager, + AdditionalServiceItemRepository additionalServiceItemRepository, + OrderSummaryGenerator orderSummaryGenerator, + TransactionRepository transactionRepository, + AdminJobQueueRepository adminJobQueueRepository, + PurchaseContextManager purchaseContextManager, + Json json) { + this.ticketReservationRepository = ticketReservationRepository; + this.userRepository = userRepository; + this.extensionManager = extensionManager; + this.auditingRepository = auditingRepository; + this.clockProvider = clockProvider; + this.configurationManager = configurationManager; + this.subscriptionRepository = subscriptionRepository; + this.ticketRepository = ticketRepository; + this.reservationOperationHelper = reservationEmailContentHelper; + this.specialPriceRepository = specialPriceRepository; + this.waitingQueueManager = waitingQueueManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.reservationCostCalculator = reservationCostCalculator; + this.billingDocumentManager = billingDocumentManager; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.transactionRepository = transactionRepository; + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + this.transactionTemplate = new TransactionTemplate(transactionManager, definition); + this.orderSummaryGenerator = orderSummaryGenerator; + this.reservationHelper = reservationEmailContentHelper; + this.auditingHelper = new ReservationAuditingHelper(auditingRepository); + this.adminJobQueueRepository = adminJobQueueRepository; + this.purchaseContextManager = purchaseContextManager; + this.json = json; + } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void finalizeCommandReceived(FinalizeReservation finalizeReservation) { + transactionTemplate.executeWithoutResult(ctx -> processFinalizeReservation(finalizeReservation, ctx, true)); + } + + public void retryFinalizeReservation(RetryFinalizeReservation retryFinalizeReservation) { + var purchaseContextAndReservation = purchaseContextManager.getReservationWithPurchaseContext(retryFinalizeReservation.getReservationId()).orElseThrow(); + var reservation = purchaseContextAndReservation.getRight(); + var purchaseContext = purchaseContextAndReservation.getLeft(); + var costResult = reservationCostCalculator.totalReservationCostWithVAT(reservation); + var totalPrice = costResult.getLeft(); + var orderSummary = orderSummaryGenerator.orderSummaryForReservation(reservation, purchaseContext); + var paymentSpecification = new PaymentSpecification(reservation, totalPrice, purchaseContext, null, orderSummary, retryFinalizeReservation.isTcAccepted(), retryFinalizeReservation.isPrivacyPolicyAccepted()); + transactionTemplate.executeWithoutResult(ctx -> processFinalizeReservation(new FinalizeReservation(paymentSpecification, retryFinalizeReservation.getPaymentProxy(), retryFinalizeReservation.isSendReservationConfirmationEmail(), retryFinalizeReservation.isSendTickets(), retryFinalizeReservation.getUsername(), retryFinalizeReservation.getOriginalStatus()), ctx, false)); + } + + private void processFinalizeReservation(FinalizeReservation finalizeReservation, TransactionStatus ctx, boolean scheduleRetryOnError) { + Object savepoint = ctx.createSavepoint(); + var spec = finalizeReservation.getPaymentSpecification(); + try { + var reservation = ticketReservationRepository.findReservationById(spec.getReservationId()); + var totalPrice = reservationCostCalculator.totalReservationCostWithVAT(reservation); + var metadata = ticketReservationRepository.getMetadata(reservation.getId()); + // generate invoice number + if (reservation.getStatus() != COMPLETE && StringUtils.isBlank(reservation.getInvoiceNumber()) && !metadata.isReadyForConfirmation()) { + boolean traceEnabled = log.isTraceEnabled(); + if (traceEnabled) { + log.trace("Generating invoice number for reservation {}", reservation.getId()); + } + var invoiceNumberOptional = billingDocumentManager.generateInvoiceNumber(spec, totalPrice.getKey()); + invoiceNumberOptional.ifPresent(s -> setInvoiceNumber(spec.getReservationId(), s)); + } + // no exceptions here. We can save the progress + ticketReservationRepository.setMetadata(reservation.getId(), metadata.withReadyForConfirmation(true)); + ctx.releaseSavepoint(savepoint); + savepoint = ctx.createSavepoint(); + + // complete reservation + completeReservation(finalizeReservation); + } catch(Exception e) { + ctx.rollbackToSavepoint(savepoint); + if (!scheduleRetryOnError) { + throw e; + } + boolean scheduled = AdminJobManager.executionScheduler( + RETRY_RESERVATION_CONFIRMATION, + Map.of("payload", json.asJsonString(RetryFinalizeReservation.fromFinalizeReservation(finalizeReservation))), + ZonedDateTime.now(clockProvider.getClock()).plusSeconds(2L) + ).apply(adminJobQueueRepository); + if(!scheduled) { + log.warn("Cannot schedule retry for reservation {}", spec.getReservationId()); + // throw exception only if we can't schedule the retry + throw e; + } else { + log.warn("Error while confirming reservation "+ spec.getReservationId() + ". Will retry in 2s", e); + } + } finally { + ctx.releaseSavepoint(savepoint); + } + } + + + private void setInvoiceNumber(String reservationId, String invoiceNumber) { + if (log.isTraceEnabled()) { + log.trace("Set invoice number {} for reservation {}", invoiceNumber, reservationId); + } + ticketReservationRepository.setInvoiceNumber(reservationId, invoiceNumber); + } + + private void completeReservation(FinalizeReservation finalizeReservation) { + var spec = finalizeReservation.getPaymentSpecification(); + var paymentProxy = finalizeReservation.getPaymentProxy(); + var username = finalizeReservation.getUsername(); + var reservationId = spec.getReservationId(); + var purchaseContext = spec.getPurchaseContext(); + final TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId); + if (reservation.getStatus() != FINALIZING && reservation.getStatus() != OFFLINE_FINALIZING) { + throw new IncompatibleStateException("Status " + reservation.getStatus() + " is not compatible with finalization."); + } + var metadata = ticketReservationRepository.getMetadata(reservationId); + if (!metadata.isReadyForConfirmation()) { + throw new IncompatibleStateException("Reservation is not ready to be confirmed"); + } + // retrieve reservation owner if username is null + Integer userId; + if(username != null) { + userId = userRepository.getByUsername(username).getId(); + } else { + userId = ticketReservationRepository.getReservationOwnerAndOrganizationId(reservationId) + .map(UserIdAndOrganizationId::getUserId) + .orElse(null); + } + ticketReservationRepository.setMetadata(reservationId, metadata.withFinalized(true)); + Locale locale = LocaleUtil.forLanguageTag(reservation.getUserLanguage()); + List<Ticket> tickets = null; + if(paymentProxy != PaymentProxy.OFFLINE) { + ticketReservationRepository.updateReservationStatus(reservationId, COMPLETE.name()); + tickets = acquireItems(paymentProxy, reservationId, spec.getEmail(), spec.getCustomerName(), spec.getLocale().getLanguage(), spec.getBillingAddress(), spec.getCustomerReference(), spec.getPurchaseContext(), finalizeReservation.isSendTickets()); + extensionManager.handleReservationConfirmation(reservation, ticketReservationRepository.getBillingDetailsForReservation(reservationId), spec.getPurchaseContext()); + } else { + // if paymentProxy is offline, we set the appropriate status to wait for payment + ticketReservationRepository.updateReservationStatus(reservationId, finalizeReservation.getOriginalStatus().name()); + } + + Date eventTime = new Date(); + auditingRepository.insert(reservationId, userId, purchaseContext, Audit.EventType.RESERVATION_COMPLETE, eventTime, Audit.EntityType.RESERVATION, reservationId); + ticketReservationRepository.updateRegistrationTimestamp(reservationId, ZonedDateTime.now(clockProvider.withZone(spec.getPurchaseContext().getZoneId()))); + if(spec.isTcAccepted()) { + auditingRepository.insert(reservationId, userId, purchaseContext, Audit.EventType.TERMS_CONDITION_ACCEPTED, eventTime, Audit.EntityType.RESERVATION, reservationId, singletonList(singletonMap("termsAndConditionsUrl", spec.getPurchaseContext().getTermsAndConditionsUrl()))); + } + + if(hasPrivacyPolicy(spec.getPurchaseContext()) && spec.isPrivacyAccepted()) { + auditingRepository.insert(reservationId, userId, purchaseContext, Audit.EventType.PRIVACY_POLICY_ACCEPTED, eventTime, Audit.EntityType.RESERVATION, reservationId, singletonList(singletonMap("privacyPolicyUrl", spec.getPurchaseContext().getPrivacyPolicyUrl()))); + } + + if(finalizeReservation.isSendReservationConfirmationEmail()) { + TicketReservation updatedReservation = ticketReservationRepository.findReservationById(reservationId); + sendConfirmationEmailIfNecessary(updatedReservation, tickets, purchaseContext, locale, username); + reservationOperationHelper.sendReservationCompleteEmailToOrganizer(spec.getPurchaseContext(), updatedReservation, locale, username); + } + } + + public void sendConfirmationEmailIfNecessary(TicketReservation ticketReservation, + List<Ticket> tickets, + PurchaseContext purchaseContext, + Locale locale, + String username) { + if (!ticketReservationRepository.getMetadata(ticketReservation.getId()).isFinalized()) { + throw new IncompatibleStateException("Reservation confirmed but not yet finalized"); + } + if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { + var config = configurationManager.getFor(List.of(SEND_RESERVATION_EMAIL_IF_NECESSARY, SEND_TICKETS_AUTOMATICALLY), purchaseContext.getConfigurationLevel()); + if(ticketReservation.getSrcPriceCts() > 0 + || CollectionUtils.isEmpty(tickets) || tickets.size() > 1 + || !tickets.get(0).getEmail().equals(ticketReservation.getEmail()) + || !config.get(SEND_RESERVATION_EMAIL_IF_NECESSARY).getValueAsBooleanOrDefault() + || !config.get(SEND_TICKETS_AUTOMATICALLY).getValueAsBooleanOrDefault() + ) { + reservationOperationHelper.sendConfirmationEmail(purchaseContext, ticketReservation, locale, username); + } + } else { + reservationOperationHelper.sendConfirmationEmail(purchaseContext, ticketReservation, locale, username); + } + } + + public void acquireSpecialPriceTokens(String reservationId) { + specialPriceRepository.updateStatusForReservation(singletonList(reservationId), SpecialPrice.Status.TAKEN.toString()); + } + + private List<Ticket> acquireItems(PaymentProxy paymentProxy, String reservationId, String email, CustomerName customerName, + String userLanguage, String billingAddress, String customerReference, PurchaseContext purchaseContext, boolean sendTickets) { + switch (purchaseContext.getType()) { + case event -> acquireEventTickets(paymentProxy, reservationId, purchaseContext, purchaseContext.event().orElseThrow()); + case subscription -> acquireSubscription(paymentProxy, reservationId, purchaseContext, customerName, email); + default -> throw new IllegalStateException("not supported purchase context"); + } + + acquireSpecialPriceTokens(reservationId); + ZonedDateTime timestamp = ZonedDateTime.now(clockProvider.getClock()); + int updatedReservation = ticketReservationRepository.updateTicketReservation(reservationId, COMPLETE.toString(), email, + customerName.getFullName(), customerName.getFirstName(), customerName.getLastName(), userLanguage, billingAddress, timestamp, paymentProxy.toString(), customerReference); + + Validate.isTrue(updatedReservation == 1, "expected exactly one updated reservation, got " + updatedReservation); + + waitingQueueManager.fireReservationConfirmed(reservationId); + //we must notify the plugins about ticket assignment and send them by email + TicketReservation reservation = findById(reservationId).orElseThrow(IllegalStateException::new); + List<Ticket> assignedTickets = findTicketsInReservation(reservationId); + assignedTickets.stream() + .filter(ticket -> StringUtils.isNotBlank(ticket.getFullName()) || StringUtils.isNotBlank(ticket.getFirstName()) || StringUtils.isNotBlank(ticket.getEmail())) + .forEach(ticket -> { + var event = purchaseContext.event().orElseThrow(); + Locale locale = LocaleUtil.forLanguageTag(ticket.getUserLanguage()); + var additionalInfo = reservationOperationHelper.retrieveAttendeeAdditionalInfoForTicket(ticket); + if((paymentProxy != PaymentProxy.ADMIN || sendTickets) && configurationManager.getFor(SEND_TICKETS_AUTOMATICALLY, ConfigurationLevel.event(event)).getValueAsBooleanOrDefault()) { + reservationOperationHelper.sendTicketByEmail(ticket, locale, event, reservationHelper.getTicketEmailGenerator(event, reservation, locale, additionalInfo)); + } + extensionManager.handleTicketAssignment(ticket, ticketCategoryRepository.getById(ticket.getCategoryId()), additionalInfo); + }); + return assignedTickets; + } + + private void acquireSubscription(PaymentProxy paymentProxy, String reservationId, PurchaseContext purchaseContext, CustomerName customerName, String email) { + var status = paymentProxy.isDeskPaymentRequired() ? AllocationStatus.TO_BE_PAID : AllocationStatus.ACQUIRED; + var subscriptionDescriptor = (SubscriptionDescriptor) purchaseContext; + ZonedDateTime validityFrom = null; + ZonedDateTime validityTo = null; + var confirmationTimestamp = subscriptionDescriptor.now(clockProvider); + if(subscriptionDescriptor.getValidityFrom() != null) { + validityFrom = subscriptionDescriptor.getValidityFrom(); + validityTo = subscriptionDescriptor.getValidityTo(); + } else if(subscriptionDescriptor.getValidityUnits() != null) { + validityFrom = confirmationTimestamp; + var temporalUnit = requireNonNullElse(subscriptionDescriptor.getValidityTimeUnit(), SubscriptionDescriptor.SubscriptionTimeUnit.DAYS).getTemporalUnit(); + validityTo = confirmationTimestamp.plus(subscriptionDescriptor.getValidityUnits(), temporalUnit) + .with(ChronoField.HOUR_OF_DAY, 23) + .with(ChronoField.MINUTE_OF_HOUR, 59) + .with(ChronoField.SECOND_OF_MINUTE, 59); + } + var subscription = subscriptionRepository.findSubscriptionsByReservationId(reservationId).stream().findFirst().orElseThrow(); + var updatedSubscriptions = subscriptionRepository.confirmSubscription(reservationId, + status, + requireNonNullElse(subscription.getFirstName(), customerName.getFirstName()), + requireNonNullElse(subscription.getLastName(), customerName.getLastName()), + requireNonNullElse(subscription.getEmail(), email), + subscriptionDescriptor.getMaxEntries(), + validityFrom, + validityTo, + confirmationTimestamp, + subscriptionDescriptor.getTimeZone()); + Validate.isTrue(updatedSubscriptions > 0, "must have updated at least one subscription"); + subscription = subscriptionRepository.findSubscriptionsByReservationId(reservationId).get(0); // at the moment it's safe because there can be only one subscription per reservation + var subscriptionId = subscription.getId(); + auditingRepository.insert(reservationId, null, purchaseContext, SUBSCRIPTION_ACQUIRED, new Date(), Audit.EntityType.SUBSCRIPTION, subscriptionId.toString()); + extensionManager.handleSubscriptionAssignmentMetadata(subscription, subscriptionDescriptor, subscriptionRepository.getSubscriptionMetadata(subscriptionId)) + .ifPresent(metadata -> subscriptionRepository.setMetadataForSubscription(subscriptionId, metadata)); + } + + private void acquireEventTickets(PaymentProxy paymentProxy, String reservationId, PurchaseContext purchaseContext, Event event) { + Ticket.TicketStatus ticketStatus = paymentProxy.isDeskPaymentRequired() ? Ticket.TicketStatus.TO_BE_PAID : Ticket.TicketStatus.ACQUIRED; + AdditionalServiceItem.AdditionalServiceItemStatus asStatus = paymentProxy.isDeskPaymentRequired() ? AdditionalServiceItem.AdditionalServiceItemStatus.TO_BE_PAID : AdditionalServiceItem.AdditionalServiceItemStatus.ACQUIRED; + Map<Integer, Ticket> preUpdateTicket = ticketRepository.findTicketsInReservation(reservationId).stream().collect(toMap(Ticket::getId, Function.identity())); + int updatedTickets = ticketRepository.updateTicketsStatusWithReservationId(reservationId, ticketStatus.toString()); + if(!configurationManager.getFor(ENABLE_TICKET_TRANSFER, purchaseContext.getConfigurationLevel()).getValueAsBooleanOrDefault()) { + //automatically lock assignment + int locked = ticketRepository.forbidReassignment(preUpdateTicket.keySet()); + Validate.isTrue(updatedTickets == locked, "Expected to lock "+updatedTickets+" tickets, locked "+ locked); + Map<Integer, Ticket> postUpdateTicket = ticketRepository.findTicketsInReservation(reservationId).stream().collect(toMap(Ticket::getId, Function.identity())); + + postUpdateTicket.forEach( + (id, ticket) -> auditingHelper.auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticket, Collections.emptyMap(), event.getId())); + } + var ticketsWithMetadataById = ticketRepository.findTicketsInReservationWithMetadata(reservationId) + .stream().collect(toMap(twm -> twm.getTicket().getId(), Function.identity())); + ticketsWithMetadataById.forEach((id, ticketWithMetadata) -> { + var newMetadataOptional = extensionManager.handleTicketAssignmentMetadata(ticketWithMetadata, event); + newMetadataOptional.ifPresent(metadata -> { + var existingContainer = TicketMetadataContainer.copyOf(ticketWithMetadata.getMetadata()); + var general = new HashMap<>(existingContainer.getMetadataForKey(TicketMetadataContainer.GENERAL) + .orElseGet(TicketMetadata::empty).getAttributes()); + general.putAll(metadata.getAttributes()); + existingContainer.putMetadata(TicketMetadataContainer.GENERAL, new TicketMetadata(null, null, general)); + ticketRepository.updateTicketMetadata(id, existingContainer); + auditingHelper.auditUpdateMetadata(reservationId, id, event.getId(), existingContainer, ticketWithMetadata.getMetadata()); + }); + auditingHelper.auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticketWithMetadata.getTicket(), Collections.emptyMap(), event.getId()); + }); + int updatedAS = additionalServiceItemRepository.updateItemsStatusWithReservationUUID(reservationId, asStatus); + Validate.isTrue(updatedTickets + updatedAS > 0, "no items have been updated"); + } + + public void confirmOfflinePayment(Event event, + String reservationId, + TransactionMetadataModification transactionMetadataModification, + String username) { + TicketReservation ticketReservation = findById(reservationId).orElseThrow(IllegalArgumentException::new); + ticketReservationRepository.lockReservationForUpdate(reservationId); + var metadata = ticketReservationRepository.getMetadata(reservationId); + if (!metadata.isReadyForConfirmation()) { + throw new IncompatibleStateException("Reservation is not ready to be confirmed"); + } + Validate.isTrue(ticketReservation.getPaymentMethod() == PaymentProxy.OFFLINE, "invalid payment method"); + Validate.isTrue(ticketReservation.isPendingOfflinePayment(), "invalid status"); + + + ticketReservationRepository.confirmOfflinePayment(reservationId, COMPLETE.name(), event.now(clockProvider)); + + registerAlfioTransaction(event, reservationId, transactionMetadataModification, PaymentProxy.OFFLINE); + + auditingRepository.insert(reservationId, userRepository.findIdByUserName(username).orElse(null), event.getId(), Audit.EventType.RESERVATION_OFFLINE_PAYMENT_CONFIRMED, new Date(), Audit.EntityType.RESERVATION, ticketReservation.getId()); + + ticketReservationRepository.setMetadata(reservationId, metadata.withFinalized(true)); + CustomerName customerName = new CustomerName(ticketReservation.getFullName(), ticketReservation.getFirstName(), ticketReservation.getLastName(), event.mustUseFirstAndLastName()); + acquireItems(PaymentProxy.OFFLINE, reservationId, ticketReservation.getEmail(), customerName, + ticketReservation.getUserLanguage(), ticketReservation.getBillingAddress(), + ticketReservation.getCustomerReference(), event, true); + + Locale language = ReservationUtil.getReservationLocale(ticketReservation); + final TicketReservation finalReservation = ticketReservationRepository.findReservationById(reservationId); + billingDocumentManager.createBillingDocument(event, finalReservation, username, orderSummaryGenerator.orderSummaryForReservation(finalReservation, event)); + var configuration = configurationManager.getFor(EnumSet.of(DEFERRED_BANK_TRANSFER_ENABLED, DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL), ConfigurationLevel.event(event)); + if(!configuration.get(DEFERRED_BANK_TRANSFER_ENABLED).getValueAsBooleanOrDefault() || configuration.get(DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL).getValueAsBooleanOrDefault()) { + reservationHelper.sendConfirmationEmail(event, findById(reservationId).orElseThrow(IllegalArgumentException::new), language, username); + } + extensionManager.handleReservationConfirmation(finalReservation, ticketReservationRepository.getBillingDetailsForReservation(reservationId), event); + } + + public void registerAlfioTransaction(Event event, + String reservationId, + TransactionMetadataModification transactionMetadataModification, + PaymentProxy paymentProxy) { + var totalPrice = reservationCostCalculator.totalReservationCostWithVAT(reservationId).getLeft(); + int priceWithVAT = totalPrice.priceWithVAT(); + long platformFee = FeeCalculator.getCalculator(event, configurationManager, requireNonNullElse(totalPrice.currencyCode(), event.getCurrency())) + .apply(ticketRepository.countTicketsInReservation(reservationId), (long) priceWithVAT) + .orElse(0L); + + //FIXME we must support multiple transactions for a reservation, otherwise we can't handle properly the case of ON_SITE payments + + var transactionOptional = transactionRepository.loadOptionalByReservationId(reservationId); + String transactionId = paymentProxy.getKey() + "-" + System.currentTimeMillis(); + var transactionTimestamp = getTransactionTimestamp(event, transactionMetadataModification); + if(transactionOptional.isEmpty()) { + transactionRepository.insert(transactionId, null, reservationId, transactionTimestamp, + priceWithVAT, event.getCurrency(), "Offline payment confirmed for "+reservationId, paymentProxy.getKey(), + platformFee, 0L, Transaction.Status.COMPLETE, buildTransactionMetadata(transactionMetadataModification)); + } else if(paymentProxy == PaymentProxy.OFFLINE) { + var transaction = transactionOptional.get(); + transactionRepository.update(transaction.getId(), transactionId, null, transactionTimestamp, + platformFee, 0L, Transaction.Status.COMPLETE, buildTransactionMetadata(transactionMetadataModification)); + } else { + log.warn("ON-Site check-in: ignoring transaction registration for reservationId {}", reservationId); + } + } + + private ZonedDateTime getTransactionTimestamp(Event event, TransactionMetadataModification transactionMetadataModification) { + if (transactionMetadataModification != null && transactionMetadataModification.getTimestamp() != null) { + return transactionMetadataModification.getTimestamp().toLocalDateTime().atZone(event.getZoneId()); + } + return event.now(clockProvider); + } + + private Map<String, String> buildTransactionMetadata(TransactionMetadataModification transactionMetadataModification) { + if (transactionMetadataModification != null && StringUtils.isNotBlank(transactionMetadataModification.getNotes())) { + return Map.of(Transaction.NOTES_KEY, transactionMetadataModification.getNotes()); + } + return Map.of(); + } + + private Optional<TicketReservation> findById(String reservationId) { + return ticketReservationRepository.findOptionalReservationById(reservationId); + } + + private List<Ticket> findTicketsInReservation(String reservationId) { + return ticketRepository.findTicketsInReservation(reservationId); + } + + +} diff --git a/src/main/java/alfio/manager/ReverseChargeManager.java b/src/main/java/alfio/manager/ReverseChargeManager.java index c7c5f0944c..59061e82a7 100644 --- a/src/main/java/alfio/manager/ReverseChargeManager.java +++ b/src/main/java/alfio/manager/ReverseChargeManager.java @@ -17,11 +17,17 @@ package alfio.manager; import alfio.controller.form.ContactAndTicketsForm; +import alfio.controller.support.CustomBindingResult; import alfio.manager.system.ConfigurationManager; import alfio.manager.system.ReservationPriceCalculator; import alfio.model.*; +import alfio.model.decorator.TicketPriceContainer; +import alfio.model.extension.CustomTaxPolicy; import alfio.repository.*; -import lombok.AllArgsConstructor; +import alfio.util.MonetaryUtil; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Component; @@ -29,23 +35,23 @@ import org.springframework.validation.ValidationUtils; import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import static alfio.model.Audit.EntityType.RESERVATION; import static alfio.model.PriceContainer.VatStatus.*; -import static alfio.model.PriceContainer.VatStatus.NOT_INCLUDED_NOT_CHARGED; import static alfio.model.system.ConfigurationKeys.*; import static alfio.util.MonetaryUtil.unitToCents; import static java.util.stream.Collectors.toMap; import static org.apache.commons.lang3.StringUtils.trimToNull; -@AllArgsConstructor @Component public class ReverseChargeManager { + private static final Logger log = LoggerFactory.getLogger(ReverseChargeManager.class); private final PromoCodeDiscountRepository promoCodeDiscountRepository; private final AdditionalServiceItemRepository additionalServiceItemRepository; private final AdditionalServiceRepository additionalServiceRepository; @@ -57,8 +63,33 @@ public class ReverseChargeManager { private final TicketReservationManager ticketReservationManager; private final TicketRepository ticketRepository; private final SubscriptionRepository subscriptionRepository; - - + private final AuditingRepository auditingRepository; + + public ReverseChargeManager(PromoCodeDiscountRepository promoCodeDiscountRepository, + AdditionalServiceItemRepository additionalServiceItemRepository, + AdditionalServiceRepository additionalServiceRepository, + TicketCategoryRepository ticketCategoryRepository, + NamedParameterJdbcTemplate jdbcTemplate, + ConfigurationManager configurationManager, + TicketReservationRepository ticketReservationRepository, + EuVatChecker vatChecker, + TicketReservationManager ticketReservationManager, + TicketRepository ticketRepository, + SubscriptionRepository subscriptionRepository, + AuditingRepository auditingRepository) { + this.promoCodeDiscountRepository = promoCodeDiscountRepository; + this.additionalServiceItemRepository = additionalServiceItemRepository; + this.additionalServiceRepository = additionalServiceRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.jdbcTemplate = jdbcTemplate; + this.configurationManager = configurationManager; + this.ticketReservationRepository = ticketReservationRepository; + this.vatChecker = vatChecker; + this.ticketReservationManager = ticketReservationManager; + this.ticketRepository = ticketRepository; + this.subscriptionRepository = subscriptionRepository; + this.auditingRepository = auditingRepository; + } public void checkAndApplyVATRules(PurchaseContext purchaseContext, @@ -88,85 +119,179 @@ public void checkAndApplyVATRules(PurchaseContext purchaseContext, .map(res -> ticketCategoryRepository.findCategoriesInReservation(res.getId())) .orElse(List.of()); - Optional<VatDetail> vatDetail = optionalReservation - .filter(e -> { - if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event) && (!reverseChargeInPerson || !reverseChargeOnline)) { - var eventFormat = purchaseContext.event().orElseThrow().getFormat(); - // if we find at least one category matching the criteria, then we can proceed - return categoriesList.stream().anyMatch(findReverseChargeCategory(reverseChargeInPerson, reverseChargeOnline, eventFormat)); - } - var vatStatus = purchaseContext.getVatStatus(); - return vatStatus == INCLUDED || vatStatus == NOT_INCLUDED; - }) - .filter(e -> vatChecker.isReverseChargeEnabledFor(purchaseContext)) - .flatMap(e -> vatChecker.checkVat(contactAndTicketsForm.getVatNr(), country, purchaseContext)); + List<Integer> noTaxCategories = new ArrayList<>(); + if (!categoriesList.isEmpty()) { + noTaxCategories.addAll(configurationManager.getCategoriesWithNoTaxes(categoriesList.stream().map(TicketCategory::getId).collect(Collectors.toList()))); + } + Optional<VatDetail> vatDetail = performReverseChargeCheck(purchaseContext, contactAndTicketsForm, country, reverseChargeInPerson, reverseChargeOnline, optionalReservation, categoriesList); if(vatDetail.isPresent()) { - var vatValidation = vatDetail.get(); - if (!vatValidation.isValid()) { - bindingResult.rejectValue("vatNr", "error.STEP_2_INVALID_VAT"); - } else { - var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); - var currencyCode = reservation.getCurrencyCode(); - PriceContainer.VatStatus vatStatus = determineVatStatus(purchaseContext.getVatStatus(), vatValidation.isVatExempt()); - // standard case: Reverse Charge is applied to the entire reservation - var discount = reservation.getPromoCodeDiscountId() != null ? promoCodeDiscountRepository.findById(reservation.getPromoCodeDiscountId()) : null; - if(!isEvent || (reverseChargeOnline && reverseChargeInPerson)) { - if(isEvent) { - var event = purchaseContext.event().orElseThrow(); - var priceContainers = mapPriceContainersByCategoryId(categoriesList, - (a) -> true, - currencyCode, - vatStatus, - discount, - event); - // update all tickets in reservation to match the VAT_STATUS - ticketRepository.updateVatStatusForReservation(reservationId, vatStatus); - updateTicketPricesByCategory(reservationId, currencyCode, vatStatus, event, priceContainers); - } - updateBillingData(reservationId, contactAndTicketsForm, purchaseContext, country, trimToNull(vatValidation.getVatNr()), reservation, vatStatus); - } else { - var event = purchaseContext.event().orElseThrow(); - var eventFormat = event.getFormat(); - var matchingCategories = mapPriceContainersByCategoryId(categoriesList, - findReverseChargeCategory(reverseChargeInPerson, reverseChargeOnline, eventFormat), - currencyCode, - vatStatus, - discount, - event); - - updateTicketPricesByCategory(reservationId, currencyCode, vatStatus, event, matchingCategories); - // update billing data for the reservation, using the original VatStatus from reservation - updateBillingData(reservationId, contactAndTicketsForm, purchaseContext, country, trimToNull(vatValidation.getVatNr()), reservation, reservation.getVatStatus()); - } - vatChecker.logSuccessfulValidation(vatValidation, reservationId, purchaseContext); - } - } else if(optionalReservation.isPresent() && contactAndTicketsForm.isItalyEInvoicingSplitPayment()) { + handleValidationResult(purchaseContext, reservationId, contactAndTicketsForm, bindingResult, country, isEvent, reverseChargeInPerson, reverseChargeOnline, categoriesList, noTaxCategories, vatDetail.get()); + } else if (optionalReservation.isPresent() && contactAndTicketsForm.isItalyEInvoicingSplitPayment()) { + handleSplitPayment(purchaseContext, reservationId, contactAndTicketsForm, country, optionalReservation.get(), categoriesList, noTaxCategories); + } else if (optionalReservation.isPresent() && !noTaxCategories.isEmpty()) { + // if there is at least one category with custom tax policy, we need to update its tickets var reservation = optionalReservation.get(); - var vatStatus = purchaseContext.getVatStatus() == INCLUDED ? INCLUDED_NOT_CHARGED : NOT_INCLUDED_NOT_CHARGED; - updateBillingData(reservationId, contactAndTicketsForm, purchaseContext, country, trimToNull(contactAndTicketsForm.getVatNr()), reservation, vatStatus); + updateTicketsWithNoTaxSetting(purchaseContext, reservationId, reservation, categoriesList, noTaxCategories); + updateBillingData(contactAndTicketsForm, purchaseContext, country, trimToNull(contactAndTicketsForm.getVatNr()), reservation, reservation.getVatStatus()); } } catch (IllegalStateException ise) {//vat checker failure bindingResult.rejectValue("vatNr", "error.vatVIESDown"); } } + private Optional<VatDetail> performReverseChargeCheck(PurchaseContext purchaseContext, ContactAndTicketsForm contactAndTicketsForm, String country, boolean reverseChargeInPerson, boolean reverseChargeOnline, Optional<TicketReservation> optionalReservation, List<TicketCategory> categoriesList) { + return optionalReservation + .filter(e -> { + if (purchaseContext.ofType(PurchaseContext.PurchaseContextType.event) && (!reverseChargeInPerson || !reverseChargeOnline)) { + var eventFormat = purchaseContext.event().orElseThrow().getFormat(); + // if we find at least one category matching the criteria, then we can proceed + return categoriesList.stream().anyMatch(findReverseChargeCategory(reverseChargeInPerson, reverseChargeOnline, eventFormat)); + } + var vatStatus = purchaseContext.getVatStatus(); + return vatStatus == INCLUDED || vatStatus == NOT_INCLUDED; + }) + .filter(e -> vatChecker.isReverseChargeEnabledFor(purchaseContext)) + .flatMap(e -> vatChecker.checkVat(contactAndTicketsForm.getVatNr(), country, purchaseContext)); + } + + private void updateTicketsWithNoTaxSetting(PurchaseContext purchaseContext, + String reservationId, + TicketReservation reservation, + List<TicketCategory> categoriesList, + List<Integer> noTaxCategories) { + if (purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { + var currencyCode = reservation.getCurrencyCode(); + var event = purchaseContext.event().orElseThrow(); + var priceContainers = mapPriceContainersByCategoryId(categoriesList, + c -> noTaxCategories.contains(c.getId()), + currencyCode, + reservation.getVatStatus(), + reservation.getDiscount().orElse(null), + event, + noTaxCategories); + updateTicketPricesByCategory(reservationId, currencyCode, event, priceContainers); + } + } + + private void handleSplitPayment(PurchaseContext purchaseContext, String reservationId, ContactAndTicketsForm contactAndTicketsForm, String country, TicketReservation reservation, List<TicketCategory> categoriesList, List<Integer> noTaxCategories) { + updateTicketsWithNoTaxSetting(purchaseContext, reservationId, reservation, categoriesList, noTaxCategories); + var vatStatus = purchaseContext.getVatStatus() == INCLUDED ? INCLUDED_NOT_CHARGED : NOT_INCLUDED_NOT_CHARGED; + updateBillingData(contactAndTicketsForm, purchaseContext, country, trimToNull(contactAndTicketsForm.getVatNr()), reservation, vatStatus); + } + + private void handleValidationResult(PurchaseContext purchaseContext, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, String country, boolean isEvent, boolean reverseChargeInPerson, boolean reverseChargeOnline, List<TicketCategory> categoriesList, List<Integer> noTaxCategories, VatDetail vatValidation) { + if (!vatValidation.isValid()) { + bindingResult.rejectValue("vatNr", "error.STEP_2_INVALID_VAT"); + } else { + var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); + var currencyCode = reservation.getCurrencyCode(); + PriceContainer.VatStatus vatStatus = determineVatStatus(purchaseContext.getVatStatus(), vatValidation.isVatExempt()); + // standard case: Reverse Charge is applied to the entire reservation + var discount = getDiscountOrNull(reservation); + if(!isEvent || (reverseChargeOnline && reverseChargeInPerson)) { + if(isEvent) { + var event = purchaseContext.event().orElseThrow(); + var priceContainers = mapPriceContainersByCategoryId(categoriesList, + a -> true, + currencyCode, + vatStatus, + discount, + event, + noTaxCategories); + // update all tickets in reservation to match the VAT_STATUS + ticketRepository.updateVatStatusForReservation(reservationId, vatStatus); + updateTicketPricesByCategory(reservationId, currencyCode, event, priceContainers); + } + updateBillingData(contactAndTicketsForm, purchaseContext, country, trimToNull(vatValidation.getVatNr()), reservation, vatStatus); + } else { + var event = purchaseContext.event().orElseThrow(); + var eventFormat = event.getFormat(); + var matchingCategories = mapPriceContainersByCategoryId(categoriesList, + findReverseChargeCategory(reverseChargeInPerson, reverseChargeOnline, eventFormat), + currencyCode, + vatStatus, + discount, + event, + noTaxCategories); + + updateTicketPricesByCategory(reservationId, currencyCode, event, matchingCategories); + // update billing data for the reservation, using the original VatStatus from reservation + updateBillingData(contactAndTicketsForm, purchaseContext, country, trimToNull(vatValidation.getVatNr()), reservation, reservation.getVatStatus()); + } + vatChecker.logSuccessfulValidation(vatValidation, reservationId, purchaseContext); + } + } + + private PromoCodeDiscount getDiscountOrNull(TicketReservation reservation) { + if (reservation.getPromoCodeDiscountId() != null) { + return promoCodeDiscountRepository.findById(reservation.getPromoCodeDiscountId()); + } + return null; + } + + public void applyCustomTaxPolicy(PurchaseContext purchaseContext, + CustomTaxPolicy customTaxPolicy, + String reservationId, + ContactAndTicketsForm contactAndTicketsForm, + CustomBindingResult bindingResult) { + + if (!purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { + throw new IllegalStateException("Custom tax policy is only supported for events"); + } + + var event = (Event) purchaseContext; + // first, validate that categories in CustomTaxPolicy are actually present in the form + var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); + var currencyCode = reservation.getCurrencyCode(); + var ticketsInReservation = ticketRepository.findTicketsInReservation(reservationId).stream() + .collect(toMap(Ticket::getUuid, Function.identity())); + var ticketIds = ticketsInReservation.keySet(); + if (customTaxPolicy.getTicketPolicies().stream().anyMatch(tp -> !ticketIds.contains(tp.getUuid()))) { + log.warn("Error in custom tax policy: some tickets are not included in reservation {}", reservationId); + bindingResult.reject("error.generic"); + } else { + // log the received policy to the auditing + auditingRepository.insert(reservationId, null, purchaseContext, Audit.EventType.VAT_CUSTOM_CONFIGURATION_APPLIED, new Date(), RESERVATION, reservationId, List.of(Map.of("policy", customTaxPolicy))); + var priceMapping = customTaxPolicy.getTicketPolicies().stream() + .map(tcp -> toTicketPriceContainer(ticketsInReservation.get(tcp.getUuid()), tcp, getDiscountOrNull(reservation), event)) + .collect(Collectors.toList()); + updateTicketPrices(priceMapping, currencyCode, event); + // update billing data for the reservation, using the original VatStatus from reservation + updateBillingData(contactAndTicketsForm, purchaseContext, reservation.getVatCountryCode(), trimToNull(reservation.getVatNr()), reservation, reservation.getVatStatus()); + } + } + + private static TicketPriceContainer toTicketPriceContainer(Ticket ticket, + CustomTaxPolicy.TicketTaxPolicy categoryTaxPolicy, + PromoCodeDiscount discount, + Event event) { + return TicketPriceContainer.from( + ticket.withVatStatus(categoryTaxPolicy.getTaxPolicy()), + categoryTaxPolicy.getTaxPolicy(), + event.getVat(), + event.getVatStatus(), + discount + ); + } + public void resetVat(PurchaseContext purchaseContext, String reservationId) { if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { var reservation = ticketReservationRepository.findReservationById(reservationId); var categoriesList = ticketCategoryRepository.findCategoriesInReservation(reservationId); - var discount = reservation.getPromoCodeDiscountId() != null ? promoCodeDiscountRepository.findById(reservation.getPromoCodeDiscountId()) : null; + var discount = getDiscountOrNull(reservation); var event = purchaseContext.event().orElseThrow(); var priceContainers = mapPriceContainersByCategoryId(categoriesList, (a) -> true, reservation.getCurrencyCode(), reservation.getVatStatus(), discount, - event); + event, + List.of()); // update all tickets in reservation to match the VAT_STATUS ticketRepository.updateVatStatusForReservation(reservationId, reservation.getVatStatus()); - updateTicketPricesByCategory(reservationId, reservation.getCurrencyCode(), reservation.getVatStatus(), event, priceContainers); + updateTicketPricesByCategory(reservationId, reservation.getCurrencyCode(), event, priceContainers); } } @@ -175,36 +300,56 @@ private Map<Integer, TicketCategoryPriceContainer> mapPriceContainersByCategoryI String currencyCode, PriceContainer.VatStatus vatStatus, PromoCodeDiscount discount, - Event event) { + Event event, + List<Integer> noTaxCategories) { return categoriesList.stream() .filter(filter) - .map(c -> new TicketCategoryPriceContainer(c.getId(), c.getSrcPriceCts(), currencyCode, event.getVat(), vatStatus, discount)) + .map(c -> { + var categoryVatStatus = vatStatus; + if (noTaxCategories.contains(c.getId())) { + categoryVatStatus = PriceContainer.VatStatus.forceExempt(vatStatus); + } + return new TicketCategoryPriceContainer(c.getId(), c.getSrcPriceCts(), currencyCode, event.getVat(), categoryVatStatus, discount); + }) .collect(toMap(o -> o.categoryId, Function.identity())); } private void updateTicketPricesByCategory(String reservationId, String currencyCode, - PriceContainer.VatStatus vatStatus, Event event, Map<Integer, TicketCategoryPriceContainer> matchingCategories) { MapSqlParameterSource[] parameterSources = matchingCategories.entrySet().stream() - .map(entry -> { - var value = entry.getValue(); - return new MapSqlParameterSource() - .addValue("reservationId", reservationId) - .addValue("categoryId", entry.getKey()) - .addValue("eventId", event.getId()) - .addValue("srcPriceCts", value.getSrcPriceCts()) - .addValue("finalPriceCts", value.getFinalPriceCts()) - .addValue("vatCts", value.getVatCts()) - .addValue("discountCts", value.getDiscountCts()) - .addValue("currencyCode", currencyCode) - .addValue("vatStatus", vatStatus.name()); - } - ).toArray(MapSqlParameterSource[]::new); + .map(entry -> buildParameterSourceForPriceUpdate(entry.getValue(), event.getId(), entry.getKey(), currencyCode, + m -> m.addValue("reservationId", reservationId))) + .toArray(MapSqlParameterSource[]::new); jdbcTemplate.batchUpdate(ticketRepository.updateTicketPriceForCategoryInReservation(), parameterSources); } + private void updateTicketPrices(List<TicketPriceContainer> ticketPrices, String currencyCode, Event event) { + MapSqlParameterSource[] parameterSources = ticketPrices.stream() + .map(entry -> buildParameterSourceForPriceUpdate(entry, event.getId(), entry.getCategoryId(), currencyCode, + m -> m.addValue("uuid", entry.getUuid()))) + .toArray(MapSqlParameterSource[]::new); + var updateResult = jdbcTemplate.batchUpdate(ticketRepository.bulkUpdateTicketPrice(), parameterSources); + Validate.isTrue(Arrays.stream(updateResult).allMatch(i -> i == 1), "Error while updating ticket prices"); + } + + private static MapSqlParameterSource buildParameterSourceForPriceUpdate(PriceContainer value, + int eventId, + int categoryId, + String currencyCode, + UnaryOperator<MapSqlParameterSource> modifier) { + return modifier.apply(new MapSqlParameterSource() + .addValue("categoryId", categoryId) + .addValue("eventId", eventId) + .addValue("srcPriceCts", value.getSrcPriceCts()) + .addValue("finalPriceCts", MonetaryUtil.unitToCents(value.getFinalPrice(), currencyCode)) + .addValue("vatCts", MonetaryUtil.unitToCents(value.getVAT(), currencyCode)) + .addValue("discountCts", MonetaryUtil.unitToCents(value.getAppliedDiscount(), currencyCode)) + .addValue("currencyCode", currencyCode) + .addValue("vatStatus", value.getVatStatus().name())); + } + private Predicate<TicketCategory> findReverseChargeCategory(boolean reverseChargeInPerson, boolean reverseChargeOnline, Event.EventFormat eventFormat) { return tc -> { if (eventFormat == Event.EventFormat.HYBRID) { @@ -215,8 +360,9 @@ private Predicate<TicketCategory> findReverseChargeCategory(boolean reverseCharg }; } - private void updateBillingData(String reservationId, ContactAndTicketsForm contactAndTicketsForm, PurchaseContext purchaseContext, String country, String vatNr, TicketReservation reservation, PriceContainer.VatStatus vatStatus) { - var discount = reservation.getPromoCodeDiscountId() != null ? promoCodeDiscountRepository.findById(reservation.getPromoCodeDiscountId()) : null; + private void updateBillingData(ContactAndTicketsForm contactAndTicketsForm, PurchaseContext purchaseContext, String country, String vatNr, TicketReservation reservation, PriceContainer.VatStatus vatStatus) { + var reservationId = reservation.getId(); + var discount = getDiscountOrNull(reservation); var additionalServiceItems = additionalServiceItemRepository.findByReservationUuid(reservation.getId()); var tickets = ticketReservationManager.findTicketsInReservation(reservation.getId()); var additionalServices = purchaseContext.event().map(event -> additionalServiceRepository.loadAllForEvent(event.getId())).orElse(List.of()); diff --git a/src/main/java/alfio/manager/SameCountryValidator.java b/src/main/java/alfio/manager/SameCountryValidator.java index 5b9c2ce5b6..5399c127fd 100644 --- a/src/main/java/alfio/manager/SameCountryValidator.java +++ b/src/main/java/alfio/manager/SameCountryValidator.java @@ -17,22 +17,20 @@ package alfio.manager; import alfio.manager.system.ConfigurationManager; -import alfio.model.Event; -import alfio.model.EventAndOrganizationId; import alfio.model.PurchaseContext; import alfio.model.VatDetail; import ch.digitalfondue.vatchecker.EUVatCheckResponse; import ch.digitalfondue.vatchecker.EUVatChecker; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.function.Predicate; -@RequiredArgsConstructor -@Log4j2 public class SameCountryValidator implements Predicate<String> { + private static final Logger log = LoggerFactory.getLogger(SameCountryValidator.class); + private final ConfigurationManager configurationManager; private final ExtensionManager extensionManager; private final PurchaseContext purchaseContext; @@ -40,6 +38,18 @@ public class SameCountryValidator implements Predicate<String> { private final EuVatChecker checker; private final EUVatChecker client = new EUVatChecker(); + public SameCountryValidator(ConfigurationManager configurationManager, + ExtensionManager extensionManager, + PurchaseContext purchaseContext, + String ticketReservationId, + EuVatChecker checker) { + this.configurationManager = configurationManager; + this.extensionManager = extensionManager; + this.purchaseContext = purchaseContext; + this.ticketReservationId = ticketReservationId; + this.checker = checker; + } + @Override public boolean test(String vatNr) { diff --git a/src/main/java/alfio/manager/SpecialPriceManager.java b/src/main/java/alfio/manager/SpecialPriceManager.java index 0aec6a04d9..f94cc31aa5 100644 --- a/src/main/java/alfio/manager/SpecialPriceManager.java +++ b/src/main/java/alfio/manager/SpecialPriceManager.java @@ -27,7 +27,6 @@ import alfio.util.LocaleUtil; import alfio.util.TemplateManager; import alfio.util.TemplateResource; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.stereotype.Component; @@ -38,10 +37,8 @@ import java.util.stream.Stream; import static alfio.model.system.ConfigurationKeys.USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL; -import static java.util.stream.Collectors.toList; @Component -@AllArgsConstructor @Transactional public class SpecialPriceManager { @@ -55,15 +52,33 @@ public class SpecialPriceManager { private final ConfigurationManager configurationManager; private final ClockProvider clockProvider; + public SpecialPriceManager(EventManager eventManager, + NotificationManager notificationManager, + SpecialPriceRepository specialPriceRepository, + TemplateManager templateManager, + MessageSourceManager messageSourceManager, + I18nManager i18nManager, + ConfigurationManager configurationManager, + ClockProvider clockProvider) { + this.eventManager = eventManager; + this.notificationManager = notificationManager; + this.specialPriceRepository = specialPriceRepository; + this.templateManager = templateManager; + this.messageSourceManager = messageSourceManager; + this.i18nManager = i18nManager; + this.configurationManager = configurationManager; + this.clockProvider = clockProvider; + } + private List<String> checkCodeAssignment(Set<SendCodeModification> input, int categoryId, EventAndOrganizationId event, String username) { final TicketCategory category = checkOwnership(categoryId, event, username); List<String> availableCodes = specialPriceRepository.findActiveByCategoryIdForUpdate(category.getId(), input.size()) .stream() - .map(SpecialPrice::getCode).collect(toList()); + .map(SpecialPrice::getCode).toList(); Validate.isTrue(input.size() <= availableCodes.size(), "Requested codes: "+input.size()+ ", available: "+availableCodes.size()+"."); - List<String> requestedCodes = input.stream().filter(IS_CODE_PRESENT).map(SendCodeModification::getCode).collect(toList()); + List<String> requestedCodes = input.stream().filter(IS_CODE_PRESENT).map(SendCodeModification::getCode).toList(); Validate.isTrue(requestedCodes.stream().distinct().count() == requestedCodes.size(), "Cannot assign the same code twice. Please fix the input file."); - Validate.isTrue(availableCodes.containsAll(requestedCodes), "some requested codes don't exist."); + Validate.isTrue(new HashSet<>(availableCodes).containsAll(requestedCodes), "some requested codes don't exist."); return availableCodes; } @@ -82,14 +97,14 @@ public List<SendCodeModification> linkAssigneeToCode(List<SendCodeModification> final Iterator<String> codes = availableCodes.iterator(); return Stream.concat(set.stream().filter(IS_CODE_PRESENT), input.stream().filter(IS_CODE_PRESENT.negate()) .map(p -> new SendCodeModification(codes.next(), p.getAssignee(), p.getEmail(), p.getLanguage()))) - .collect(toList()); + .toList(); } public List<SpecialPrice> loadSentCodes(String eventName, int categoryId, String username) { final EventAndOrganizationId event = eventManager.getEventAndOrganizationId(eventName, username); checkOwnership(categoryId, event, username); Predicate<SpecialPrice> p = SpecialPrice::notSent; - return specialPriceRepository.findAllByCategoryId(categoryId).stream().filter(p.negate()).collect(toList()); + return specialPriceRepository.findAllByCategoryId(categoryId).stream().filter(p.negate()).toList(); } public boolean clearRecipientData(String eventName, int categoryId, int codeId, String username) { diff --git a/src/main/java/alfio/manager/SpecialPriceTokenGenerator.java b/src/main/java/alfio/manager/SpecialPriceTokenGenerator.java index 82bff9ad6d..dfd4decc7c 100644 --- a/src/main/java/alfio/manager/SpecialPriceTokenGenerator.java +++ b/src/main/java/alfio/manager/SpecialPriceTokenGenerator.java @@ -25,9 +25,10 @@ import alfio.repository.EventRepository; import alfio.repository.SpecialPriceRepository; import alfio.repository.TicketCategoryRepository; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.text.RandomStringGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Component; @@ -41,10 +42,11 @@ * granting a special price to a specific user category. */ @Component -@Log4j2 @Transactional public class SpecialPriceTokenGenerator { + private static final Logger log = LoggerFactory.getLogger(SpecialPriceTokenGenerator.class); + private static final SecureRandom RANDOM = new SecureRandom(); private static final char[] ADMITTED_CHARACTERS = new char[]{ 'A', 'B', 'C', 'D', 'E', 'F', diff --git a/src/main/java/alfio/manager/SubscriptionManager.java b/src/main/java/alfio/manager/SubscriptionManager.java index 784d7cf52b..4f0186e4db 100644 --- a/src/main/java/alfio/manager/SubscriptionManager.java +++ b/src/main/java/alfio/manager/SubscriptionManager.java @@ -16,16 +16,21 @@ */ package alfio.manager; +import alfio.config.Initializer; import alfio.controller.form.SearchOptions; import alfio.model.AllocationStatus; import alfio.model.modification.SubscriptionDescriptorModification; +import alfio.model.result.ErrorCode; +import alfio.model.result.Result; import alfio.model.subscription.EventSubscriptionLink; import alfio.model.subscription.SubscriptionDescriptor; import alfio.model.subscription.SubscriptionDescriptorWithStatistics; import alfio.repository.SubscriptionRepository; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Component; @@ -35,22 +40,28 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.stream.Stream; import static java.util.Objects.requireNonNullElse; @Component @Transactional -@AllArgsConstructor -@Log4j2 public class SubscriptionManager { + private static final Logger log = LoggerFactory.getLogger(SubscriptionManager.class); + private final SubscriptionRepository subscriptionRepository; private final NamedParameterJdbcTemplate jdbcTemplate; + private final Environment environment; + + public SubscriptionManager(SubscriptionRepository subscriptionRepository, + NamedParameterJdbcTemplate jdbcTemplate, + Environment environment) { + this.subscriptionRepository = subscriptionRepository; + this.jdbcTemplate = jdbcTemplate; + this.environment = environment; + } public List<SubscriptionDescriptor> findAll(int organizationId) { return subscriptionRepository.findAllByOrganizationIds(organizationId); @@ -183,6 +194,12 @@ public Optional<UUID> updateSubscriptionDescriptor(SubscriptionDescriptorModific if(original.getPrice() != subscriptionDescriptor.getPriceCts()) { subscriptionRepository.updatePriceForSubscriptions(subscriptionDescriptorId, subscriptionDescriptor.getPriceCts()); } + if(!Objects.equals(original.getMaxEntries(), subscriptionDescriptor.getMaxEntries())) { + int maxEntries = requireNonNullElse(subscriptionDescriptor.getMaxEntries(), -1); + int updatedSubscriptions = subscriptionRepository.updateMaxEntriesForSubscriptions(subscriptionDescriptorId, maxEntries); + log.debug("SubscriptionDescriptor #{}: updated {} subscriptions. Modified max entries to {}", + subscriptionDescriptorId, updatedSubscriptions, maxEntries); + } return Optional.of(subscriptionDescriptorId); }); } @@ -192,6 +209,9 @@ public Optional<SubscriptionDescriptor> findOne(UUID id, int organizationId) { } public boolean setPublicStatus(UUID id, int organizationId, boolean isPublic) { + if(environment.acceptsProfiles(Profiles.of(Initializer.PROFILE_DEMO))) { + throw new IllegalStateException("Cannot publish subscriptions while in demo mode"); + } return subscriptionRepository.setPublicStatus(id, organizationId, isPublic) == 1; } @@ -207,6 +227,10 @@ public List<SubscriptionDescriptorWithStatistics> loadSubscriptionsWithStatistic return subscriptionRepository.findAllWithStatistics(organizationId); } + public Optional<SubscriptionDescriptorWithStatistics> loadSubscriptionWithStatistics(UUID id, int organizationId) { + return subscriptionRepository.findOneWithStatistics(id, organizationId); + } + public int linkSubscriptionToEvent(UUID subscriptionId, int eventId, int organizationId, int pricePerTicket) { return subscriptionRepository.linkSubscriptionAndEvent(subscriptionId, eventId, pricePerTicket, organizationId); } @@ -222,4 +246,43 @@ public int countFree(UUID subscriptionDescriptorId) { public List<SubscriptionDescriptor> loadActiveSubscriptionDescriptors(int organizationId) { return subscriptionRepository.findActiveSubscriptionsForOrganization(organizationId); } + + public Result<Boolean> deactivateDescriptor(int organizationId, UUID descriptorId) { + // 1. remove all links + removeAllEventLinksForSubscription(organizationId, descriptorId); + int result = subscriptionRepository.deactivateDescriptor(descriptorId, organizationId); + if (result == 1) { + return Result.success(true); + } + return Result.error(ErrorCode.custom("cannot-deactivate-subscription", + "Cannot deactivate subscription descriptor")); + } + + public Result<List<EventSubscriptionLink>> updateLinkedEvents(int organizationId, UUID subscriptionId, List<Integer> eventIds){ + if (eventIds.isEmpty()) { + removeAllEventLinksForSubscription(organizationId, subscriptionId); + return Result.success(List.of()); + } else { + subscriptionRepository.removeStaleEvents(subscriptionId, organizationId, eventIds); + var parameters = eventIds.stream() + .map(eventId -> new MapSqlParameterSource("eventId", eventId) + .addValue("subscriptionId", subscriptionId) + .addValue("pricePerTicket", 0) + .addValue("organizationId", organizationId)) + .toArray(MapSqlParameterSource[]::new); + var result = jdbcTemplate.batchUpdate(SubscriptionRepository.INSERT_SUBSCRIPTION_LINK, parameters); + return new Result.Builder<List<EventSubscriptionLink>>() + .checkPrecondition(() -> Arrays.stream(result).allMatch(r -> r == 1), ErrorCode.custom("cannot-link", "Cannot link events")) + .build(() -> getLinkedEvents(organizationId, subscriptionId)); + } + } + + public SubscriptionDescriptor findDescriptorBySubscriptionId(UUID subscriptionId) { + return subscriptionRepository.findDescriptorBySubscriptionId(subscriptionId); + } + + private void removeAllEventLinksForSubscription(int organizationId, UUID subscriptionId) { + int removed = subscriptionRepository.removeAllLinksForSubscription(subscriptionId, organizationId); + log.info("removed all event links ({}) for subscription {}", removed, subscriptionId); + } } diff --git a/src/main/java/alfio/manager/TicketReservationManager.java b/src/main/java/alfio/manager/TicketReservationManager.java index 557e77e8d8..c464fc9e50 100644 --- a/src/main/java/alfio/manager/TicketReservationManager.java +++ b/src/main/java/alfio/manager/TicketReservationManager.java @@ -18,45 +18,38 @@ import alfio.controller.api.support.TicketHelper; import alfio.controller.form.UpdateTicketOwnerForm; -import alfio.controller.support.TemplateProcessor; import alfio.manager.PaymentManager.PaymentMethodDTO.PaymentMethodStatus; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.payment.BankTransferManager; import alfio.manager.payment.PaymentSpecification; import alfio.manager.support.*; +import alfio.manager.support.reservation.*; import alfio.manager.system.ConfigurationLevel; import alfio.manager.system.ConfigurationManager; -import alfio.manager.system.Mailer; -import alfio.manager.system.ReservationPriceCalculator; import alfio.manager.user.UserManager; import alfio.model.*; import alfio.model.AdditionalServiceItem.AdditionalServiceItemStatus; import alfio.model.PriceContainer.VatStatus; import alfio.model.PromoCodeDiscount.CodeType; -import alfio.model.PromoCodeDiscount.DiscountType; import alfio.model.PurchaseContext.PurchaseContextType; import alfio.model.SpecialPrice.Status; import alfio.model.SummaryRow.SummaryType; import alfio.model.Ticket.TicketStatus; import alfio.model.TicketReservation.TicketReservationStatus; -import alfio.model.checkin.OnlineCheckInFullInfo; -import alfio.model.decorator.AdditionalServiceItemPriceContainer; +import alfio.model.checkin.CheckInFullInfo; import alfio.model.decorator.AdditionalServicePriceContainer; import alfio.model.decorator.TicketPriceContainer; -import alfio.model.extension.CustomEmailText; import alfio.model.group.LinkedGroup; +import alfio.model.metadata.SubscriptionMetadata; import alfio.model.metadata.TicketMetadata; import alfio.model.metadata.TicketMetadataContainer; -import alfio.model.modification.ASReservationWithOptionalCodeModification; -import alfio.model.modification.AdditionalServiceReservationModification; -import alfio.model.modification.TicketReservationWithOptionalCodeModification; +import alfio.model.modification.*; import alfio.model.result.ErrorCode; import alfio.model.result.Result; import alfio.model.result.WarningMessage; import alfio.model.subscription.*; -import alfio.model.subscription.SubscriptionDescriptor.SubscriptionTimeUnit; -import alfio.model.support.UserIdAndOrganizationId; -import alfio.model.system.ConfigurationKeys; +import alfio.model.system.command.FinalizeReservation; +import alfio.model.system.command.InvalidateAccess; import alfio.model.transaction.*; import alfio.model.transaction.capabilities.OfflineProcessor; import alfio.model.transaction.capabilities.ServerInitiatedTransaction; @@ -68,7 +61,6 @@ import alfio.repository.user.UserRepository; import alfio.util.*; import alfio.util.checkin.TicketCheckInUtil; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -76,6 +68,10 @@ import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -89,17 +85,14 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.validation.BindingResult; -import org.springframework.web.util.UriComponentsBuilder; import java.math.BigDecimal; import java.security.Principal; import java.time.Clock; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.util.*; -import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Predicate; @@ -108,19 +101,22 @@ import java.util.stream.Stream; import static alfio.model.Audit.EntityType.RESERVATION; -import static alfio.model.Audit.EntityType.TICKET; import static alfio.model.Audit.EventType.*; -import static alfio.model.BillingDocument.Type.*; +import static alfio.model.BillingDocument.Type.CREDIT_NOTE; import static alfio.model.PromoCodeDiscount.categoriesOrNull; import static alfio.model.TicketReservation.TicketReservationStatus.*; import static alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType.ONCE_PER_EVENT; import static alfio.model.system.ConfigurationKeys.*; -import static alfio.util.MonetaryUtil.*; +import static alfio.util.MiscUtils.getAtIndexOrNull; +import static alfio.util.MonetaryUtil.formatUnit; +import static alfio.util.MonetaryUtil.unitToCents; +import static alfio.util.ReservationUtil.getReservationLocale; +import static alfio.util.ReservationUtil.hasPrivacyPolicy; import static alfio.util.Wrappers.optionally; import static alfio.util.checkin.TicketCheckInUtil.ticketOnlineCheckInUrl; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.*; import static org.apache.commons.lang3.StringUtils.*; @@ -129,10 +125,11 @@ @Component @Transactional -@Log4j2 @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class TicketReservationManager { - + + private static final Logger log = LoggerFactory.getLogger(TicketReservationManager.class); + public static final String NOT_YET_PAID_TRANSACTION_ID = "not-paid"; private static final String STUCK_TICKETS_MSG = "there are stuck tickets for the event %s. Please check admin area."; private static final String STUCK_TICKETS_SUBJECT = "warning: stuck tickets found"; @@ -174,26 +171,12 @@ public class TicketReservationManager { private final PurchaseContextManager purchaseContextManager; private final SubscriptionRepository subscriptionRepository; private final UserManager userManager; - - public static class NotEnoughTicketsException extends RuntimeException { - - } - - public static class MissingSpecialPriceTokenException extends RuntimeException { - } - - public static class InvalidSpecialPriceTokenException extends RuntimeException { - - } - - public static class TooManyTicketsForDiscountCodeException extends RuntimeException { - } - - public static class CannotProceedWithPayment extends RuntimeException { - CannotProceedWithPayment(String message) { - super(message); - } - } + private final ApplicationEventPublisher applicationEventPublisher; + private final ReservationEmailContentHelper reservationHelper; + private final ReservationCostCalculator reservationCostCalculator; + private final OrderSummaryGenerator orderSummaryGenerator; + private final ReservationAuditingHelper auditingHelper; + private final ReservationFinalizer reservationFinalizer; public TicketReservationManager(EventRepository eventRepository, OrganizationRepository organizationRepository, @@ -226,7 +209,12 @@ public TicketReservationManager(EventRepository eventRepository, ClockProvider clockProvider, PurchaseContextManager purchaseContextManager, SubscriptionRepository subscriptionRepository, - UserManager userManager) { + UserManager userManager, + ApplicationEventPublisher applicationEventPublisher, + ReservationCostCalculator reservationCostCalculator, + ReservationEmailContentHelper reservationHelper, + ReservationFinalizer reservationFinalizer, + OrderSummaryGenerator orderSummaryGenerator) { this.eventRepository = eventRepository; this.organizationRepository = organizationRepository; this.ticketRepository = ticketRepository; @@ -264,12 +252,19 @@ public TicketReservationManager(EventRepository eventRepository, this.purchaseContextManager = purchaseContextManager; this.subscriptionRepository = subscriptionRepository; this.userManager = userManager; + this.applicationEventPublisher = applicationEventPublisher; + this.reservationCostCalculator = reservationCostCalculator; + this.orderSummaryGenerator = orderSummaryGenerator; + this.reservationHelper = reservationHelper; + this.auditingHelper = new ReservationAuditingHelper(auditingRepository); + this.reservationFinalizer = reservationFinalizer; } private String createSubscriptionReservation(SubscriptionDescriptor subscriptionDescriptor, Date reservationExpiration, Locale locale, - Integer userId) throws CannotProceedWithPayment, NotEnoughTicketsException { + Integer userId, + SubscriptionMetadata metadata) throws CannotProceedWithPayment, NotEnoughTicketsException { String reservationId = UUID.randomUUID().toString(); ticketReservationRepository.createNewReservation(reservationId, subscriptionDescriptor.now(clockProvider), @@ -281,24 +276,32 @@ private String createSubscriptionReservation(SubscriptionDescriptor subscription subscriptionDescriptor.getCurrency(), subscriptionDescriptor.getOrganizationId(), userId); + UUID subscriptionId; if(subscriptionDescriptor.getMaxAvailable() > 0) { var optionalSubscription = subscriptionRepository.selectFreeSubscription(subscriptionDescriptor.getId()); if(optionalSubscription.isEmpty()) { throw new NotEnoughTicketsException(); } - Validate.isTrue(subscriptionRepository.bindSubscriptionToReservation(reservationId, subscriptionDescriptor.getPrice(), AllocationStatus.PENDING, optionalSubscription.get()) == 1); + var subscription = optionalSubscription.get(); + Validate.isTrue(subscriptionRepository.bindSubscriptionToReservation(reservationId, subscriptionDescriptor.getPrice(), AllocationStatus.PENDING, subscription) == 1); + subscriptionId = subscription; } else { - subscriptionRepository.createSubscription(UUID.randomUUID(), subscriptionDescriptor.getId(), reservationId, subscriptionDescriptor.getMaxEntries(), + subscriptionId = UUID.randomUUID(); + subscriptionRepository.createSubscription(subscriptionId, subscriptionDescriptor.getId(), reservationId, subscriptionDescriptor.getMaxEntries(), subscriptionDescriptor.getValidityFrom(), subscriptionDescriptor.getValidityTo(), subscriptionDescriptor.getPrice(), subscriptionDescriptor.getCurrency(), subscriptionDescriptor.getOrganizationId(), AllocationStatus.PENDING, subscriptionDescriptor.getMaxEntries(), subscriptionDescriptor.getTimeZone()); } var totalPrice = totalReservationCostWithVAT(reservationId).getLeft(); var vatStatus = subscriptionDescriptor.getVatStatus(); - ticketReservationRepository.updateBillingData(subscriptionDescriptor.getVatStatus(), calculateSrcPrice(vatStatus, totalPrice), totalPrice.getPriceWithVAT(), totalPrice.getVAT(), Math.abs(totalPrice.getDiscount()), subscriptionDescriptor.getCurrency(), null, null, false, reservationId); + ticketReservationRepository.updateBillingData(subscriptionDescriptor.getVatStatus(), calculateSrcPrice(vatStatus, totalPrice), totalPrice.priceWithVAT(), totalPrice.VAT(), Math.abs(totalPrice.discount()), subscriptionDescriptor.getCurrency(), null, null, false, reservationId); auditingRepository.insert(reservationId, null, subscriptionDescriptor.event().map(Event::getId).orElse(null), Audit.EventType.RESERVATION_CREATE, new Date(), Audit.EntityType.RESERVATION, reservationId); if (!canProceedWithPayment(subscriptionDescriptor, totalPrice, reservationId)) { throw new CannotProceedWithPayment("No payment method applicable for purchase context " + subscriptionDescriptor.getType() + " with public id " + subscriptionDescriptor.getPublicIdentifier()); } + // update metadata if present + if (metadata != null) { + Validate.isTrue(subscriptionRepository.setMetadataForSubscription(subscriptionId, metadata) == 1); + } return reservationId; } @@ -359,7 +362,7 @@ public String createTicketReservation(Event event, additionalServices.forEach(as -> reserveAdditionalServicesForReservation(event.getId(), reservationId, as, discount.orElse(null))); var totalPrice = totalReservationCostWithVAT(reservationId).getLeft(); var vatStatus = event.getVatStatus(); - ticketReservationRepository.updateBillingData(event.getVatStatus(), calculateSrcPrice(vatStatus, totalPrice), totalPrice.getPriceWithVAT(), totalPrice.getVAT(), Math.abs(totalPrice.getDiscount()), event.getCurrency(), null, null, false, reservationId); + ticketReservationRepository.updateBillingData(event.getVatStatus(), calculateSrcPrice(vatStatus, totalPrice), totalPrice.priceWithVAT(), totalPrice.VAT(), Math.abs(totalPrice.discount()), event.getCurrency(), null, null, false, reservationId); auditingRepository.insert(reservationId, null, event.getId(), Audit.EventType.RESERVATION_CREATE, new Date(), Audit.EntityType.RESERVATION, reservationId); if(isDiscountCodeUsageExceeded(reservationId)) { throw new TooManyTicketsForDiscountCodeException(); @@ -401,8 +404,8 @@ Optional<String> createDynamicPromoCodeIfNeeded(Event event, List<TicketReservat } private int calculateSrcPrice(VatStatus vatStatus, TotalPrice totalPrice) { - return (vatStatus == VatStatus.INCLUDED ? totalPrice.getPriceWithVAT() : totalPrice.getPriceWithVAT() - totalPrice.getVAT()) - + Math.abs(totalPrice.getDiscount()); + return (vatStatus == VatStatus.INCLUDED ? totalPrice.priceWithVAT() : totalPrice.priceWithVAT() - totalPrice.VAT()) + + Math.abs(totalPrice.discount()); } @@ -423,7 +426,7 @@ void reserveTicketsForCategory(Event event, } else { //first check if there is another pending special price token bound to the current sessionId Optional<SpecialPrice> specialPrice = fixToken(ticketReservation.getSpecialPrice(), ticketReservation.getTicketCategoryId(), event.getId(), ticketReservation); - specialPrices = specialPrice.stream().collect(toList()); + specialPrices = specialPrice.stream().toList(); } List<Integer> reservedForUpdate = reserveTickets(event.getId(), ticketReservation, forWaitingQueue ? asList(TicketStatus.RELEASED, TicketStatus.PRE_RESERVED) : singletonList(TicketStatus.FREE)); @@ -433,27 +436,44 @@ void reserveTicketsForCategory(Event event, } TicketCategory category = ticketCategoryRepository.getByIdAndActive(ticketReservation.getTicketCategoryId(), event.getId()); - List<Map<String, String>> ticketMetadata = requireNonNullElse(ticketReservation.getMetadata(), List.of()); + initTicketsForReservation(event, reservationId, locale, accessCodeOrDiscount, specialPrices, reservedForUpdate, category, ticketReservation); + Ticket ticket = ticketRepository.findById(reservedForUpdate.get(0), category.getId()); + var discountToApply = ObjectUtils.firstNonNull(dynamicDiscount, accessCodeOrDiscount); + TicketPriceContainer priceContainer = TicketPriceContainer.from(ticket, null, event.getVat(), event.getVatStatus(), discountToApply); + var currencyCode = priceContainer.getCurrencyCode(); + ticketRepository.updateTicketPrice(reservedForUpdate, + category.getId(), + event.getId(), + category.getSrcPriceCts(), + MonetaryUtil.unitToCents(priceContainer.getFinalPrice(), currencyCode), + MonetaryUtil.unitToCents(priceContainer.getVAT(), currencyCode), + MonetaryUtil.unitToCents(priceContainer.getAppliedDiscount(), currencyCode), + category.getCurrencyCode(), + priceContainer.getVatStatus()); + } + + private void initTicketsForReservation(Event event, + String reservationId, + Locale locale, + PromoCodeDiscount accessCodeOrDiscount, + List<SpecialPrice> specialPrices, + List<Integer> reservedForUpdate, + TicketCategory category, + TicketReservationWithOptionalCodeModification ticketReservation) { + var attendees = requireNonNull(ticketReservation.getAttendees()); if (!specialPrices.isEmpty()) { if(specialPrices.size() != reservedForUpdate.size()) { throw new NotEnoughTicketsException(); } - AtomicInteger counter = new AtomicInteger(0); - var ticketsAndSpecialPrices = specialPrices.stream() - .map(sp -> { - int index = counter.getAndIncrement(); - return Triple.of(reservedForUpdate.get(index), sp, getAtIndexOrNull(ticketMetadata, index)); - }).collect(Collectors.toList()); - if(specialPrices.size() == 1) { var ticketId = reservedForUpdate.get(0); var sp = specialPrices.get(0); var accessCodeId = accessCodeOrDiscount != null && accessCodeOrDiscount.getHiddenCategoryId() != null ? accessCodeOrDiscount.getId() : null; TicketMetadata metadata = null; - var attributes = getAtIndexOrNull(ticketMetadata, 0); - if(attributes != null) { - metadata = new TicketMetadata(null, null, attributes); + var attendee = getAtIndexOrEmpty(attendees, 0); + if(attendee.hasMetadata()) { + metadata = new TicketMetadata(null, null, attendee.getMetadata()); } ticketRepository.reserveTicket(reservationId, ticketId, @@ -463,13 +483,23 @@ void reserveTicketsForCategory(Event event, category.getCurrencyCode(), event.getVatStatus(), TicketMetadataContainer.fromMetadata(metadata)); + if (attendee.hasContactData()) { + ticketRepository.updateTicketOwnerById(ticketId, attendee.getEmail(), null, attendee.getFirstName(), attendee.getLastName()); + } specialPriceRepository.updateStatus(sp.getId(), Status.PENDING.toString(), null, accessCodeId); } else { + AtomicInteger counter = new AtomicInteger(0); + var ticketsAndSpecialPrices = specialPrices.stream() + .map(sp -> { + int index = counter.getAndIncrement(); + return Triple.of(reservedForUpdate.get(index), sp, getAtIndexOrEmpty(attendees, index)); + }).toList(); jdbcTemplate.batchUpdate(ticketRepository.batchReserveTicketsForSpecialPrice(), ticketsAndSpecialPrices.stream().map( triple -> { - TicketMetadataContainer metadata = null; - if(triple.getRight() != null) { - metadata = TicketMetadataContainer.fromMetadata(new TicketMetadata(null, null, triple.getRight())); + String metadata = null; + var attendee = triple.getRight(); + if(attendee.hasMetadata()) { + metadata = json.asJsonString(TicketMetadataContainer.fromMetadata(new TicketMetadata(null, null, attendee.getMetadata()))); } return new MapSqlParameterSource(RESERVATION_ID, reservationId) .addValue("ticketId", triple.getLeft()) @@ -477,45 +507,31 @@ void reserveTicketsForCategory(Event event, .addValue("userLanguage", locale.getLanguage()) .addValue("srcPriceCts", category.getSrcPriceCts()) .addValue("currencyCode", category.getCurrencyCode()) - .addValue("ticketMetadata", json.asJsonString(metadata)) + .addValue("ticketMetadata", requireNonNullElse(metadata, "{}")) + .addValue("firstName", attendee.getFirstName()) + .addValue("lastName", attendee.getLastName()) + .addValue("email", attendee.getEmail()) .addValue("vatStatus", event.getVatStatus().toString()); } ).toArray(MapSqlParameterSource[]::new)); specialPriceRepository.batchUpdateStatus( - specialPrices.stream().map(SpecialPrice::getId).collect(toList()), + specialPrices.stream().map(SpecialPrice::getId).toList(), Status.PENDING, Objects.requireNonNull(accessCodeOrDiscount).getId()); } } else { - int reserved = ticketRepository.reserveTickets(reservationId, reservedForUpdate, category, locale.getLanguage(), event.getVatStatus(), idx -> { - var metadata = getAtIndexOrNull(ticketMetadata, idx); - if (metadata != null) { - return json.asJsonString(TicketMetadataContainer.fromMetadata(new TicketMetadata(null, null, metadata))); - } - return null; - }); + int reserved = ticketRepository.reserveTickets(reservationId, + reservedForUpdate, + category, + locale.getLanguage(), + event.getVatStatus(), + idx -> getAtIndexOrEmpty(attendees, idx)); Validate.isTrue(reserved == reservedForUpdate.size(), "Cannot reserve all tickets"); } - Ticket ticket = ticketRepository.findById(reservedForUpdate.get(0), category.getId()); - var discountToApply = ObjectUtils.firstNonNull(dynamicDiscount, accessCodeOrDiscount); - TicketPriceContainer priceContainer = TicketPriceContainer.from(ticket, null, event.getVat(), event.getVatStatus(), discountToApply); - var currencyCode = priceContainer.getCurrencyCode(); - ticketRepository.updateTicketPrice(reservedForUpdate, - category.getId(), - event.getId(), - category.getSrcPriceCts(), - MonetaryUtil.unitToCents(priceContainer.getFinalPrice(), currencyCode), - MonetaryUtil.unitToCents(priceContainer.getVAT(), currencyCode), - MonetaryUtil.unitToCents(priceContainer.getAppliedDiscount(), currencyCode), - category.getCurrencyCode(), - priceContainer.getVatStatus()); } - private static <T> T getAtIndexOrNull(List<T> elements, int index) { - if (elements == null || index >= elements.size()) { - return null; - } - return elements.get(index); + private static AttendeeData getAtIndexOrEmpty(List<AttendeeData> attendees, int index) { + return requireNonNullElse(getAtIndexOrNull(attendees, index), AttendeeData.empty()); } List<SpecialPrice> reserveTokensForAccessCode(TicketReservationWithOptionalCodeModification ticketReservation, PromoCodeDiscount accessCode) { @@ -571,7 +587,7 @@ List<Integer> reserveTickets(int eventId, TicketReservationWithOptionalCodeModif List<Integer> reserveTickets(int eventId , int categoryId, int qty, List<TicketStatus> requiredStatuses) { TicketCategory category = ticketCategoryRepository.getByIdAndActive(categoryId, eventId); - List<String> statusesAsString = requiredStatuses.stream().map(TicketStatus::name).collect(toList()); + List<String> statusesAsString = requiredStatuses.stream().map(TicketStatus::name).toList(); if(category.isBounded()) { return ticketRepository.selectTicketInCategoryForUpdateSkipLocked(eventId, categoryId, qty, statusesAsString); } @@ -657,7 +673,7 @@ public PaymentResult performPayment(PaymentSpecification spec, if (paymentResult.isSuccessful()) { reservation = ticketReservationRepository.findReservationById(spec.getReservationId()); - transitionToComplete(spec, reservationCost, paymentProxy, null); + transitionToComplete(spec, paymentProxy, null); } else if(paymentResult.isFailed()) { reTransitionToPending(spec.getReservationId()); } @@ -667,7 +683,7 @@ public PaymentResult performPayment(PaymentSpecification spec, reTransitionToPending(spec.getReservationId()); } //it is guaranteed that in this case we're dealing with "local" error (e.g. database failure), - //thus it is safer to not rollback the reservation status + //thus it is safer to not roll back the reservation status log.error("unexpected error during payment confirmation", ex); return PaymentResult.failed("error.STEP2_STRIPE_unexpected"); } @@ -710,11 +726,9 @@ public boolean cancelPendingPayment(String reservationId, PurchaseContext purcha return false; } - private void transitionToComplete(PaymentSpecification spec, TotalPrice reservationCost, PaymentProxy paymentProxy, String username) { + private void transitionToComplete(PaymentSpecification spec, PaymentProxy paymentProxy, String username) { var status = ticketReservationRepository.findOptionalStatusAndValidationById(spec.getReservationId()).orElseThrow().getStatus(); if(status != COMPLETE) { - billingDocumentManager.generateInvoiceNumber(spec, reservationCost) - .ifPresent(invoiceNumber -> ticketReservationRepository.setInvoiceNumber(spec.getReservationId(), invoiceNumber)); completeReservation(spec, paymentProxy, true, true, username); } } @@ -748,7 +762,7 @@ private PaymentProxy evaluatePaymentProxy(PaymentProxy proxy, TotalPrice reserva if(proxy != null) { return proxy; } - if(reservationCost.getPriceWithVAT() == 0) { + if(reservationCost.priceWithVAT() == 0) { return PaymentProxy.NONE; } return PaymentProxy.STRIPE; @@ -758,7 +772,7 @@ private boolean initPaymentProcess(TotalPrice reservationCost, PaymentProxy paymentProxy, PaymentSpecification spec, Principal principal) { - if(reservationCost.getPriceWithVAT() > 0 && paymentProxy == PaymentProxy.STRIPE) { + if(reservationCost.priceWithVAT() > 0 && paymentProxy == PaymentProxy.STRIPE) { try { transitionToInPayment(spec, principal); } catch (Exception e) { @@ -784,209 +798,61 @@ private boolean acquireGroupMembers(String reservationId, PurchaseContext purcha return true; } - public void confirmOfflinePayment(Event event, String reservationId, String username) { - TicketReservation ticketReservation = findById(reservationId).orElseThrow(IllegalArgumentException::new); - ticketReservationRepository.lockReservationForUpdate(reservationId); - Validate.isTrue(ticketReservation.getPaymentMethod() == PaymentProxy.OFFLINE, "invalid payment method"); - Validate.isTrue(ticketReservation.isPendingOfflinePayment(), "invalid status"); - - - ticketReservationRepository.confirmOfflinePayment(reservationId, TicketReservationStatus.COMPLETE.name(), event.now(clockProvider)); - - registerAlfioTransaction(event, reservationId, PaymentProxy.OFFLINE); - - auditingRepository.insert(reservationId, userRepository.findIdByUserName(username).orElse(null), event.getId(), Audit.EventType.RESERVATION_OFFLINE_PAYMENT_CONFIRMED, new Date(), Audit.EntityType.RESERVATION, ticketReservation.getId()); - - CustomerName customerName = new CustomerName(ticketReservation.getFullName(), ticketReservation.getFirstName(), ticketReservation.getLastName(), event.mustUseFirstAndLastName()); - acquireItems(PaymentProxy.OFFLINE, reservationId, ticketReservation.getEmail(), customerName, - ticketReservation.getUserLanguage(), ticketReservation.getBillingAddress(), - ticketReservation.getCustomerReference(), event, true); - - Locale language = findReservationLanguage(reservationId); - - final TicketReservation finalReservation = ticketReservationRepository.findReservationById(reservationId); - billingDocumentManager.createBillingDocument(event, finalReservation, username, orderSummaryForReservation(finalReservation, event)); - var configuration = configurationManager.getFor(EnumSet.of(DEFERRED_BANK_TRANSFER_ENABLED, DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL), ConfigurationLevel.event(event)); - if(!configuration.get(DEFERRED_BANK_TRANSFER_ENABLED).getValueAsBooleanOrDefault() || configuration.get(DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL).getValueAsBooleanOrDefault()) { - sendConfirmationEmail(event, findById(reservationId).orElseThrow(IllegalArgumentException::new), language, username); - } - extensionManager.handleReservationConfirmation(finalReservation, ticketReservationRepository.getBillingDetailsForReservation(reservationId), event); + public void confirmOfflinePayment(Event event, String reservationId, TransactionMetadataModification transactionMetadataModification, String username) { + reservationFinalizer.confirmOfflinePayment(event, reservationId, transactionMetadataModification, username); } - void registerAlfioTransaction(Event event, String reservationId, PaymentProxy paymentProxy) { - var totalPrice = totalReservationCostWithVAT(reservationId).getLeft(); - int priceWithVAT = totalPrice.getPriceWithVAT(); - long platformFee = FeeCalculator.getCalculator(event, configurationManager, requireNonNullElse(totalPrice.getCurrencyCode(), event.getCurrency())) - .apply(ticketRepository.countTicketsInReservation(reservationId), (long) priceWithVAT) - .orElse(0L); - - //FIXME we must support multiple transactions for a reservation, otherwise we can't handle properly the case of ON_SITE payments - - var transactionOptional = transactionRepository.loadOptionalByReservationId(reservationId); - String transactionId = paymentProxy.getKey() + "-" + System.currentTimeMillis(); - if(transactionOptional.isEmpty()) { - transactionRepository.insert(transactionId, null, reservationId, event.now(clockProvider), - priceWithVAT, event.getCurrency(), "Offline payment confirmed for "+reservationId, paymentProxy.getKey(), - platformFee, 0L, Transaction.Status.COMPLETE, Map.of()); - } else if(paymentProxy == PaymentProxy.OFFLINE) { - var transaction = transactionOptional.get(); - transactionRepository.update(transaction.getId(), transactionId, null, event.now(clockProvider), - platformFee, 0L, Transaction.Status.COMPLETE, Map.of()); - } else { - log.warn("ON-Site check-in: ignoring transaction registration for reservationId {}", reservationId); - } - + void registerAlfioTransactionForOnsitePayment(Event event, String reservationId) { + reservationFinalizer.registerAlfioTransaction(event, reservationId, null, PaymentProxy.ON_SITE); } public void sendConfirmationEmail(PurchaseContext purchaseContext, TicketReservation ticketReservation, Locale language, String username) { - String reservationId = ticketReservation.getId(); - - OrderSummary summary = orderSummaryForReservationId(reservationId, purchaseContext); - - List<Mailer.Attachment> attachments; - if (configurationManager.canGenerateReceiptOrInvoiceToCustomer(purchaseContext)) { // https://github.com/alfio-event/alf.io/issues/573 - attachments = generateAttachmentForConfirmationEmail(purchaseContext, ticketReservation, language, summary, username); - } else{ - attachments = List.of(); - } - var vat = getVAT(purchaseContext); - - List<ConfirmationEmailConfiguration> configurations = new ArrayList<>(); - if(purchaseContext.ofType(PurchaseContextType.subscription)) { - var firstSubscription = subscriptionRepository.findSubscriptionsByReservationId(reservationId).stream().findFirst().orElseThrow(); - boolean sendSeparateEmailToOwner = !Objects.equals(firstSubscription.getEmail(), ticketReservation.getEmail()); - Map<String, Object> initialModel = Map.of( - "pin", firstSubscription.getPin(), - "subscriptionId", firstSubscription.getId(), - "includePin", true, - "fullName", firstSubscription.getFirstName() + " " + firstSubscription.getLastName()); - var model = prepareModelForReservationEmail(purchaseContext, ticketReservation, vat, summary, List.of(), initialModel); - configurations.add(new ConfirmationEmailConfiguration(TemplateResource.CONFIRMATION_EMAIL_SUBSCRIPTION, firstSubscription.getEmail(), model, sendSeparateEmailToOwner ? List.of() : attachments)); - if(sendSeparateEmailToOwner) { - var separateModel = new HashMap<>(model); - separateModel.put("includePin", false); - separateModel.put("fullName", ticketReservation.getFullName()); - configurations.add(new ConfirmationEmailConfiguration(TemplateResource.CONFIRMATION_EMAIL_SUBSCRIPTION, ticketReservation.getEmail(), separateModel, attachments)); - } - } else { - var model = prepareModelForReservationEmail(purchaseContext, ticketReservation, vat, summary, ticketRepository.findTicketsInReservation(ticketReservation.getId()), Map.of()); - configurations.add(new ConfirmationEmailConfiguration(TemplateResource.CONFIRMATION_EMAIL, ticketReservation.getEmail(), model, attachments)); - } - - var messageSource = messageSourceManager.getMessageSourceFor(purchaseContext); - var localizedType = messageSource.getMessage("purchase-context."+purchaseContext.getType(), null, language); - configurations.forEach(configuration -> { - notificationManager.sendSimpleEmail(purchaseContext, ticketReservation.getId(), configuration.getEmailAddress(), messageSource.getMessage("reservation-email-subject", - new Object[]{getShortReservationID(purchaseContext, ticketReservation), purchaseContext.getTitle().get(language.getLanguage()), localizedType}, language), - () -> templateManager.renderTemplate(purchaseContext, configuration.getTemplateResource(), configuration.getModel(), language), - configuration.getAttachments()); - }); - } - - private List<Mailer.Attachment> generateAttachmentForConfirmationEmail(PurchaseContext purchaseContext, - TicketReservation ticketReservation, - Locale language, - OrderSummary summary, - String username) { - if(mustGenerateBillingDocument(summary, ticketReservation)) { //#459 - include PDF invoice in reservation email - BillingDocument.Type type = ticketReservation.getHasInvoiceNumber() ? INVOICE : RECEIPT; - return billingDocumentManager.generateBillingDocumentAttachment(purchaseContext, ticketReservation, language, type, username, summary); - } - return List.of(); + this.reservationHelper.sendConfirmationEmail(purchaseContext, ticketReservation, language, username); } - public void sendReservationCompleteEmailToOrganizer(PurchaseContext purchaseContext, TicketReservation ticketReservation, Locale language, String username) { - Organization organization = organizationRepository.getById(purchaseContext.getOrganizationId()); - List<String> cc = notificationManager.getCCForEventOrganizer(purchaseContext); - - Map<String, Object> reservationEmailModel = prepareModelForReservationEmail(purchaseContext, ticketReservation); - - String reservationId = ticketReservation.getId(); - OrderSummary summary = orderSummaryForReservationId(reservationId, purchaseContext); - - List<Mailer.Attachment> attachments = Collections.emptyList(); - - if (!configurationManager.canGenerateReceiptOrInvoiceToCustomer(purchaseContext) || configurationManager.isInvoiceOnly(purchaseContext)) { // https://github.com/alfio-event/alf.io/issues/573 - attachments = generateAttachmentForConfirmationEmail(purchaseContext, ticketReservation, language, summary, username); - } - - - String shortReservationID = configurationManager.getShortReservationID(purchaseContext, ticketReservation); - notificationManager.sendSimpleEmail(purchaseContext, null, organization.getEmail(), cc, "Reservation complete " + shortReservationID, - () -> templateManager.renderTemplate(purchaseContext, TemplateResource.CONFIRMATION_EMAIL_FOR_ORGANIZER, reservationEmailModel, language), - attachments); - } - - private static boolean mustGenerateBillingDocument(OrderSummary summary, TicketReservation ticketReservation) { - return !summary.getFree() && (!summary.getNotYetPaid() || (summary.getWaitingForPayment() && ticketReservation.isInvoiceRequested())); - } - - private List<Mailer.Attachment> generateBillingDocumentAttachment(PurchaseContext purchaseContext, - TicketReservation ticketReservation, - Locale language, - Map<String, Object> billingDocumentModel, - BillingDocument.Type documentType) { - Map<String, String> model = new HashMap<>(); - model.put(RESERVATION_ID, ticketReservation.getId()); - purchaseContext.event().ifPresent(event -> model.put("eventId", Integer.toString(event.getId()))); - model.put("language", json.asJsonString(language)); - model.put("reservationEmailModel", json.asJsonString(billingDocumentModel));//ticketReservation.getHasInvoiceNumber() - switch (documentType) { - case INVOICE: - return Collections.singletonList(new Mailer.Attachment("invoice.pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.INVOICE_PDF)); - case RECEIPT: - return Collections.singletonList(new Mailer.Attachment("receipt.pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.RECEIPT_PDF)); - case CREDIT_NOTE: - return Collections.singletonList(new Mailer.Attachment("credit-note.pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.CREDIT_NOTE_PDF)); - default: - throw new IllegalStateException(documentType+" is not supported"); - } - } private Locale findReservationLanguage(String reservationId) { - return ticketReservationRepository.findOptionalReservationById(reservationId).map(TicketReservationManager::getReservationLocale).orElse(Locale.ENGLISH); + return ticketReservationRepository.findOptionalReservationById(reservationId).map(ReservationUtil::getReservationLocale).orElse(Locale.ENGLISH); } - public void deleteOfflinePayment(Event event, String reservationId, boolean expired, boolean credit, String username) { + public void deleteOfflinePayment(Event event, String reservationId, boolean expired, boolean credit, boolean notify, String username) { TicketReservation reservation = findById(reservationId).orElseThrow(IllegalArgumentException::new); Validate.isTrue(reservation.getStatus() == OFFLINE_PAYMENT || reservation.getStatus() == DEFERRED_OFFLINE_PAYMENT, "Invalid reservation status"); Validate.isTrue(!(credit && reservation.getStatus() == DEFERRED_OFFLINE_PAYMENT), "Cannot credit deferred payment"); if(credit) { - creditReservation(reservation, username); + creditReservation(reservation, username, notify); } else { - Map<String, Object> emailModel = prepareModelForReservationEmail(event, reservation); - Locale reservationLanguage = findReservationLanguage(reservationId); - String subject = getReservationEmailSubject(event, reservationLanguage, "reservation-email-expired-subject", reservation.getId()); - notificationManager.sendSimpleEmail(event, reservationId, reservation.getEmail(), subject, - () -> templateManager.renderTemplate(event, TemplateResource.OFFLINE_RESERVATION_EXPIRED_EMAIL, emailModel, reservationLanguage) - ); + if (notify) { + Map<String, Object> emailModel = reservationHelper.prepareModelForReservationEmail(event, reservation); + Locale reservationLanguage = findReservationLanguage(reservationId); + String subject = reservationHelper.getReservationEmailSubject(event, reservationLanguage, "reservation-email-expired-subject", reservation.getId()); + notificationManager.sendSimpleEmail(event, reservationId, reservation.getEmail(), subject, + () -> templateManager.renderTemplate(event, TemplateResource.OFFLINE_RESERVATION_EXPIRED_EMAIL, emailModel, reservationLanguage) + ); + } cancelReservation(reservation, expired, username); } } - private String getReservationEmailSubject(PurchaseContext purchaseContext, Locale reservationLanguage, String key, String id) { - return messageSourceManager.getMessageSourceFor(purchaseContext) - .getMessage(key, new Object[]{id, purchaseContext.getDisplayName()}, reservationLanguage); - } - @Transactional public void issueCreditNoteForReservation(PurchaseContext purchaseContext, TicketReservation reservation, String username, boolean sendEmail) { var reservationId = reservation.getId(); ticketReservationRepository.updateReservationStatus(reservationId, TicketReservationStatus.CREDIT_NOTE_ISSUED.toString()); auditingRepository.insert(reservationId, userRepository.nullSafeFindIdByUserName(username).orElse(null), purchaseContext, Audit.EventType.CREDIT_NOTE_ISSUED, new Date(), RESERVATION, reservationId); - var model = prepareModelForReservationEmail(purchaseContext, reservation, getVAT(purchaseContext), orderSummaryForReservation(reservation, purchaseContext), ticketRepository.findTicketsInReservation(reservation.getId()), Map.of()); + var model = prepareModelForReservationEmail(purchaseContext, reservation, reservationHelper.getVAT(purchaseContext), orderSummaryForReservation(reservation, purchaseContext), ticketRepository.findTicketsInReservation(reservation.getId()), Map.of()); BillingDocument billingDocument = billingDocumentManager.createBillingDocument(purchaseContext, reservation, username, BillingDocument.Type.CREDIT_NOTE, orderSummaryForReservation(reservation, purchaseContext)); var organization = organizationRepository.getById(purchaseContext.getOrganizationId()); extensionManager.handleCreditNoteGenerated(reservation, purchaseContext, ((OrderSummary) model.get("orderSummary")).getOriginalTotalPrice(), billingDocument.getId(), Map.of(ORGANIZATION, organization)); if(sendEmail) { + var reservationLocale = getReservationLocale(reservation); notificationManager.sendSimpleEmail(purchaseContext, reservationId, reservation.getEmail(), - getReservationEmailSubject(purchaseContext, getReservationLocale(reservation), "credit-note-issued-email-subject", reservation.getId()), - () -> templateManager.renderTemplate(purchaseContext, TemplateResource.CREDIT_NOTE_ISSUED_EMAIL, model, getReservationLocale(reservation)), - generateBillingDocumentAttachment(purchaseContext, reservation, getReservationLocale(reservation), billingDocument.getModel(), CREDIT_NOTE) + reservationHelper.getReservationEmailSubject(purchaseContext, reservationLocale, "credit-note-issued-email-subject", reservation.getId()), + () -> templateManager.renderTemplate(purchaseContext, TemplateResource.CREDIT_NOTE_ISSUED_EMAIL, model, reservationLocale), + reservationHelper.generateBillingDocumentAttachment(purchaseContext, reservation, reservationLocale, billingDocument.getModel(), CREDIT_NOTE) ); } } @@ -1014,7 +880,7 @@ void issueCreditNoteForRefund(PurchaseContext purchaseContext, TicketReservation var cost = new TotalPrice(priceContainer.getSrcPriceCts(), unitToCents(priceContainer.getVAT(), currencyCode), 0, 0, currencyCode); var orderSummary = new OrderSummary( cost, - List.of(new SummaryRow(summaryRowTitle, formattedAmount, formattedPriceBeforeVat, 1, formattedAmount, formattedPriceBeforeVat, unitToCents(refundAmount, currencyCode), SummaryType.TICKET, null)), + List.of(new SummaryRow(summaryRowTitle, formattedAmount, formattedPriceBeforeVat, 1, formattedAmount, formattedPriceBeforeVat, unitToCents(refundAmount, currencyCode), SummaryType.TICKET, null, priceContainer.getVatStatus())), false, formattedAmount, formatUnit(priceContainer.getVAT(), currencyCode), @@ -1038,83 +904,16 @@ public Map<String, Object> prepareModelForReservationEmail(PurchaseContext purch OrderSummary summary, List<Ticket> ticketsToInclude, Map<String, Object> initialOptions) { - Organization organization = organizationRepository.getById(purchaseContext.getOrganizationId()); - String baseUrl = configurationManager.baseUrl(purchaseContext); - var reservationId = reservation.getId(); - String reservationUrl = reservationUrl(reservationId); - String reservationShortID = getShortReservationID(purchaseContext, reservation); - - var bankingInfo = configurationManager.getFor(Set.of(INVOICE_ADDRESS, BANK_ACCOUNT_NR, BANK_ACCOUNT_OWNER), ConfigurationLevel.purchaseContext(purchaseContext)); - Optional<String> invoiceAddress = bankingInfo.get(INVOICE_ADDRESS).getValue(); - Optional<String> bankAccountNr = bankingInfo.get(BANK_ACCOUNT_NR).getValue(); - Optional<String> bankAccountOwner = bankingInfo.get(BANK_ACCOUNT_OWNER).getValue(); - - Map<Integer, List<Ticket>> ticketsByCategory = ticketsToInclude - .stream() - .collect(groupingBy(Ticket::getCategoryId)); - final List<TicketWithCategory> ticketsWithCategory; - if(!ticketsByCategory.isEmpty()) { - ticketsWithCategory = ticketCategoryRepository.findByIds(ticketsByCategory.keySet()) - .stream() - .flatMap(tc -> ticketsByCategory.get(tc.getId()).stream().map(t -> new TicketWithCategory(t, tc))) - .collect(toList()); - } else { - ticketsWithCategory = Collections.emptyList(); - } - Map<String, Object> baseModel = new HashMap<>(); - baseModel.putAll(initialOptions); - baseModel.putAll(extensionManager.handleReservationEmailCustomText(purchaseContext, reservation, ticketReservationRepository.getAdditionalInfo(reservationId)) - .map(CustomEmailText::toMap) - .orElse(Map.of())); - Map<String, Object> model = TemplateResource.prepareModelForConfirmationEmail(organization, purchaseContext, reservation, vat, ticketsWithCategory, summary, baseUrl, reservationUrl, reservationShortID, invoiceAddress, bankAccountNr, bankAccountOwner, baseModel); - boolean euBusiness = StringUtils.isNotBlank(reservation.getVatCountryCode()) && StringUtils.isNotBlank(reservation.getVatNr()) - && configurationManager.getForSystem(ConfigurationKeys.EU_COUNTRIES_LIST).getRequiredValue().contains(reservation.getVatCountryCode()) - && VatStatus.isVatExempt(reservation.getVatStatus()); - model.put("euBusiness", euBusiness); - model.put("publicId", configurationManager.getPublicReservationID(purchaseContext, reservation)); - model.put("invoicingAdditionalInfo", loadAdditionalInfo(reservationId).getInvoicingAdditionalInfo()); - if(purchaseContext.getType() == PurchaseContextType.event) { - var event = purchaseContext.event().orElseThrow(); - model.put("displayLocation", ticketsWithCategory.stream() - .noneMatch(tc -> EventUtil.isAccessOnline(tc.getCategory(), event))); - } else { - model.put("displayLocation", false); - } - if(ticketReservationRepository.hasSubscriptionApplied(reservationId)) { - model.put("displaySubscriptionUsage", true); - var subscription = subscriptionRepository.findAppliedSubscriptionByReservationId(reservationId).orElseThrow(); - if(subscription.getMaxEntries() > -1) { - var subscriptionUsageDetails = UsageDetails.fromSubscription(subscription, ticketRepository.countSubscriptionUsage(subscription.getId(), null)); - model.put("subscriptionUsageDetails", subscriptionUsageDetails); - model.put("subscriptionUrl", reservationUrl(subscription.getReservationId())); - } - } - return model; - } - - @Transactional(readOnly = true) - public Map<String, Object> prepareModelForReservationEmail(Event event, TicketReservation reservation, Optional<String> vat, OrderSummary summary) { - - var initialOptions = extensionManager.handleReservationEmailCustomText(event, reservation, ticketReservationRepository.getAdditionalInfo(reservation.getId())) - .map(CustomEmailText::toMap) - .orElse(Map.of()); - return prepareModelForReservationEmail(event, reservation, vat, summary, ticketRepository.findTicketsInReservation(reservation.getId()), initialOptions); + return reservationHelper.prepareModelForReservationEmail(purchaseContext, reservation, vat, summary, ticketsToInclude, initialOptions); } public TicketReservationAdditionalInfo loadAdditionalInfo(String reservationId) { return ticketReservationRepository.getAdditionalInfo(reservationId); } - @Transactional(readOnly = true) - public Map<String, Object> prepareModelForReservationEmail(PurchaseContext purchaseContext, TicketReservation reservation) { - Optional<String> vat = getVAT(purchaseContext); - OrderSummary summary = orderSummaryForReservationId(reservation.getId(), purchaseContext); - return prepareModelForReservationEmail(purchaseContext, reservation, vat, summary, ticketRepository.findTicketsInReservation(reservation.getId()), Map.of()); - } - private Map<String, Object> prepareModelForPartialCreditNote(Event event, TicketReservation reservation, List<Ticket> removedTickets) { - var orderSummary = orderSummaryForCreditNote(reservation, event, removedTickets); - var optionalVat = getVAT(event); + var orderSummary = orderSummaryGenerator.orderSummaryForCreditNote(reservation, event, removedTickets); + var optionalVat = reservationHelper.getVAT(event); return prepareModelForReservationEmail(event, reservation, optionalVat, orderSummary, removedTickets, Map.of()); } @@ -1124,7 +923,7 @@ private void transitionToInPayment(PaymentSpecification spec, Principal principa if(optionalStatusAndValidation.isPresent() && optionalStatusAndValidation.get().getStatus() == COMPLETE) { // reservation has been already completed. Let's check if there is a corresponding audit event Validate.isTrue(auditingRepository.countAuditsOfTypeForReservation(spec.getReservationId(), PAYMENT_CONFIRMED) == 1, "Trying to confirm an already paid reservation, but can't find autiting event"); - } else { + } else if(optionalStatusAndValidation.isPresent() && optionalStatusAndValidation.get().getStatus() == PENDING) { int updatedReservation = ticketReservationRepository.updateTicketReservation(spec.getReservationId(), IN_PAYMENT.toString(), spec.getEmail(), spec.getCustomerName().getFullName(), spec.getCustomerName().getFirstName(), spec.getCustomerName().getLastName(), @@ -1184,208 +983,30 @@ public Optional<Triple<Event, TicketReservation, Ticket>> from(String eventName, } /** - * Set the tickets attached to the reservation to the ACQUIRED state and the ticket reservation to the COMPLETE state. Additionally it will save email/fullName/billingaddress/userLanguage. + * Set the tickets attached to the reservation to the ACQUIRED state and the ticket reservation to the COMPLETE state. + * Additionally, it will save email/fullName/billingAddress/userLanguage. */ void completeReservation(PaymentSpecification spec, PaymentProxy paymentProxy, boolean sendReservationConfirmationEmail, boolean sendTickets, String username) { - String reservationId = spec.getReservationId(); - var purchaseContext = spec.getPurchaseContext(); - final TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId); - // retrieve reservation owner if username is null - Integer userId; - if(username != null) { - userId = userRepository.getByUsername(username).getId(); - } else { - userId = ticketReservationRepository.getReservationOwnerAndOrganizationId(reservationId) - .map(UserIdAndOrganizationId::getUserId) - .orElse(null); + // pre-acquire special price tokens before committing, in order to ensure atomicity + reservationFinalizer.acquireSpecialPriceTokens(spec.getReservationId()); + // update reservation status to mark the finalization, but first retrieve the current one + // set by the payment provider. This is useful especially in case a single "PaymentProxy" can produce different statuses + // like OFFLINE_PAYMENT and DEFERRED_OFFLINE_PAYMENT + var currentStatus = ticketReservationRepository.findOptionalStatusAndValidationById(spec.getReservationId()).orElseThrow().getStatus(); + TicketReservationStatus targetStatus = FINALIZING; + if (currentStatus == OFFLINE_PAYMENT || currentStatus == DEFERRED_OFFLINE_PAYMENT) { + targetStatus = OFFLINE_FINALIZING; } - Locale locale = LocaleUtil.forLanguageTag(reservation.getUserLanguage()); - List<Ticket> tickets = null; - if(paymentProxy != PaymentProxy.OFFLINE) { - tickets = acquireItems(paymentProxy, reservationId, spec.getEmail(), spec.getCustomerName(), spec.getLocale().getLanguage(), spec.getBillingAddress(), spec.getCustomerReference(), spec.getPurchaseContext(), sendTickets); - extensionManager.handleReservationConfirmation(reservation, ticketReservationRepository.getBillingDetailsForReservation(reservationId), spec.getPurchaseContext()); - } - - Date eventTime = new Date(); - auditingRepository.insert(reservationId, userId, purchaseContext, Audit.EventType.RESERVATION_COMPLETE, eventTime, Audit.EntityType.RESERVATION, reservationId); - ticketReservationRepository.updateRegistrationTimestamp(reservationId, ZonedDateTime.now(clockProvider.withZone(spec.getPurchaseContext().getZoneId()))); - if(spec.isTcAccepted()) { - auditingRepository.insert(reservationId, userId, purchaseContext, Audit.EventType.TERMS_CONDITION_ACCEPTED, eventTime, Audit.EntityType.RESERVATION, reservationId, singletonList(singletonMap("termsAndConditionsUrl", spec.getPurchaseContext().getTermsAndConditionsUrl()))); - } - - if(eventHasPrivacyPolicy(spec.getPurchaseContext()) && spec.isPrivacyAccepted()) { - auditingRepository.insert(reservationId, userId, purchaseContext, Audit.EventType.PRIVACY_POLICY_ACCEPTED, eventTime, Audit.EntityType.RESERVATION, reservationId, singletonList(singletonMap("privacyPolicyUrl", spec.getPurchaseContext().getPrivacyPolicyUrl()))); - } - - if(sendReservationConfirmationEmail) { - TicketReservation updatedReservation = ticketReservationRepository.findReservationById(reservationId); - sendConfirmationEmailIfNecessary(updatedReservation, tickets, purchaseContext, locale, username); - sendReservationCompleteEmailToOrganizer(spec.getPurchaseContext(), updatedReservation, locale, username); - } - } - - void sendConfirmationEmailIfNecessary(TicketReservation ticketReservation, - List<Ticket> tickets, - PurchaseContext purchaseContext, - Locale locale, - String username) { - if(purchaseContext.ofType(PurchaseContextType.event)) { - var config = configurationManager.getFor(List.of(SEND_RESERVATION_EMAIL_IF_NECESSARY, SEND_TICKETS_AUTOMATICALLY), purchaseContext.getConfigurationLevel()); - if(ticketReservation.getSrcPriceCts() > 0 - || CollectionUtils.isEmpty(tickets) || tickets.size() > 1 - || !tickets.get(0).getEmail().equals(ticketReservation.getEmail()) - || !config.get(SEND_RESERVATION_EMAIL_IF_NECESSARY).getValueAsBooleanOrDefault() - || !config.get(SEND_TICKETS_AUTOMATICALLY).getValueAsBooleanOrDefault() - ) { - sendConfirmationEmail(purchaseContext, ticketReservation, locale, username); - } - } else { - sendConfirmationEmail(purchaseContext, ticketReservation, locale, username); - } - } - - private boolean eventHasPrivacyPolicy(PurchaseContext event) { - return StringUtils.isNotBlank(event.getPrivacyPolicyLinkOrNull()); - } - - private List<Ticket> acquireItems(PaymentProxy paymentProxy, String reservationId, String email, CustomerName customerName, - String userLanguage, String billingAddress, String customerReference, PurchaseContext purchaseContext, boolean sendTickets) { - switch (purchaseContext.getType()) { - case event: { - acquireEventTickets(paymentProxy, reservationId, purchaseContext, purchaseContext.event().orElseThrow()); - break; - } - case subscription: { - acquireSubscription(paymentProxy, reservationId, purchaseContext, customerName, email); - break; - } - default: throw new IllegalStateException("not supported purchase context"); - } - - specialPriceRepository.updateStatusForReservation(singletonList(reservationId), Status.TAKEN.toString()); - ZonedDateTime timestamp = ZonedDateTime.now(clockProvider.getClock()); - int updatedReservation = ticketReservationRepository.updateTicketReservation(reservationId, TicketReservationStatus.COMPLETE.toString(), email, - customerName.getFullName(), customerName.getFirstName(), customerName.getLastName(), userLanguage, billingAddress, timestamp, paymentProxy.toString(), customerReference); - - - Validate.isTrue(updatedReservation == 1, "expected exactly one updated reservation, got " + updatedReservation); - - waitingQueueManager.fireReservationConfirmed(reservationId); - //we must notify the plugins about ticket assignment and send them by email - TicketReservation reservation = findById(reservationId).orElseThrow(IllegalStateException::new); - List<Ticket> assignedTickets = findTicketsInReservation(reservationId); - assignedTickets.stream() - .filter(ticket -> StringUtils.isNotBlank(ticket.getFullName()) || StringUtils.isNotBlank(ticket.getFirstName()) || StringUtils.isNotBlank(ticket.getEmail())) - .forEach(ticket -> { - var event = purchaseContext.event().orElseThrow(); - Locale locale = LocaleUtil.forLanguageTag(ticket.getUserLanguage()); - var additionalInfo = retrieveAttendeeAdditionalInfoForTicket(ticket); - if((paymentProxy != PaymentProxy.ADMIN || sendTickets) && configurationManager.getFor(SEND_TICKETS_AUTOMATICALLY, ConfigurationLevel.event(event)).getValueAsBooleanOrDefault()) { - sendTicketByEmail(ticket, locale, event, getTicketEmailGenerator(event, reservation, locale, additionalInfo)); - } - extensionManager.handleTicketAssignment(ticket, ticketCategoryRepository.getById(ticket.getCategoryId()), additionalInfo); - }); - return assignedTickets; - } - - private void acquireSubscription(PaymentProxy paymentProxy, String reservationId, PurchaseContext purchaseContext, CustomerName customerName, String email) { - var status = paymentProxy.isDeskPaymentRequired() ? AllocationStatus.TO_BE_PAID : AllocationStatus.ACQUIRED; - var subscriptionDescriptor = (SubscriptionDescriptor) purchaseContext; - ZonedDateTime validityFrom = null; - ZonedDateTime validityTo = null; - var confirmationTimestamp = subscriptionDescriptor.now(clockProvider); - if(subscriptionDescriptor.getValidityFrom() != null) { - validityFrom = subscriptionDescriptor.getValidityFrom(); - validityTo = subscriptionDescriptor.getValidityTo(); - } else if(subscriptionDescriptor.getValidityUnits() != null) { - validityFrom = confirmationTimestamp; - var temporalUnit = requireNonNullElse(subscriptionDescriptor.getValidityTimeUnit(), SubscriptionTimeUnit.DAYS).getTemporalUnit(); - validityTo = confirmationTimestamp.plus(subscriptionDescriptor.getValidityUnits(), temporalUnit) - .with(ChronoField.HOUR_OF_DAY, 23) - .with(ChronoField.MINUTE_OF_HOUR, 59) - .with(ChronoField.SECOND_OF_MINUTE, 59); - } - var subscription = subscriptionRepository.findSubscriptionsByReservationId(reservationId).stream().findFirst().orElseThrow(); - var updatedSubscriptions = subscriptionRepository.confirmSubscription(reservationId, - status, - requireNonNullElse(subscription.getFirstName(), customerName.getFirstName()), - requireNonNullElse(subscription.getLastName(), customerName.getLastName()), - requireNonNullElse(subscription.getEmail(), email), - subscriptionDescriptor.getMaxEntries(), - validityFrom, - validityTo, - confirmationTimestamp, - subscriptionDescriptor.getTimeZone()); - subscriptionRepository.findSubscriptionsByReservationId(reservationId) // at the moment it's safe because there can be only one subscription per reservation - .forEach(subscriptionId -> auditingRepository.insert(reservationId, null, purchaseContext, SUBSCRIPTION_ACQUIRED, new Date(), Audit.EntityType.SUBSCRIPTION, subscriptionId.toString())); - Validate.isTrue(updatedSubscriptions > 0, "must have updated at least one subscription"); - } - - private void acquireEventTickets(PaymentProxy paymentProxy, String reservationId, PurchaseContext purchaseContext, Event event) { - TicketStatus ticketStatus = paymentProxy.isDeskPaymentRequired() ? TicketStatus.TO_BE_PAID : TicketStatus.ACQUIRED; - AdditionalServiceItemStatus asStatus = paymentProxy.isDeskPaymentRequired() ? AdditionalServiceItemStatus.TO_BE_PAID : AdditionalServiceItemStatus.ACQUIRED; - Map<Integer, Ticket> preUpdateTicket = ticketRepository.findTicketsInReservation(reservationId).stream().collect(toMap(Ticket::getId, Function.identity())); - int updatedTickets = ticketRepository.updateTicketsStatusWithReservationId(reservationId, ticketStatus.toString()); - if(!configurationManager.getFor(ENABLE_TICKET_TRANSFER, purchaseContext.getConfigurationLevel()).getValueAsBooleanOrDefault()) { - //automatically lock assignment - int locked = ticketRepository.forbidReassignment(preUpdateTicket.keySet()); - Validate.isTrue(updatedTickets == locked, "Expected to lock "+updatedTickets+" tickets, locked "+ locked); - Map<Integer, Ticket> postUpdateTicket = ticketRepository.findTicketsInReservation(reservationId).stream().collect(toMap(Ticket::getId, Function.identity())); - - postUpdateTicket.forEach( - (id, ticket) -> auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticket, Collections.emptyMap(), event.getId())); - } - var ticketsWithMetadataById = ticketRepository.findTicketsInReservationWithMetadata(reservationId) - .stream().collect(toMap(twm -> twm.getTicket().getId(), Function.identity())); - ticketsWithMetadataById.forEach((id, ticketWithMetadata) -> { - var newMetadataOptional = extensionManager.handleTicketAssignmentMetadata(ticketWithMetadata, event); - newMetadataOptional.ifPresent(metadata -> { - var existingContainer = TicketMetadataContainer.copyOf(ticketWithMetadata.getMetadata()); - var general = new HashMap<>(existingContainer.getMetadataForKey(TicketMetadataContainer.GENERAL) - .orElseGet(TicketMetadata::empty).getAttributes()); - general.putAll(metadata.getAttributes()); - existingContainer.putMetadata(TicketMetadataContainer.GENERAL, new TicketMetadata(null, null, general)); - ticketRepository.updateTicketMetadata(id, existingContainer); - auditUpdateMetadata(reservationId, id, event.getId(), existingContainer, ticketWithMetadata.getMetadata()); - }); - auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticketWithMetadata.getTicket(), Collections.emptyMap(), event.getId()); - }); - int updatedAS = additionalServiceItemRepository.updateItemsStatusWithReservationUUID(reservationId, asStatus); - Validate.isTrue(updatedTickets + updatedAS > 0, "no items have been updated"); + ticketReservationRepository.updateReservationStatus(spec.getReservationId(), targetStatus.name()); + // run detached reservation confirmation + this.applicationEventPublisher.publishEvent(new FinalizeReservation(spec, paymentProxy, sendReservationConfirmationEmail, sendTickets, username, currentStatus)); } public PartialTicketTextGenerator getTicketEmailGenerator(Event event, TicketReservation ticketReservation, Locale ticketLanguage, Map<String, List<String>> additionalInfo) { - return ticket -> { - Organization organization = organizationRepository.getById(event.getOrganizationId()); - String ticketUrl = ticketUpdateUrl(event, ticket.getUuid()); - var ticketCategory = ticketCategoryRepository.getById(ticket.getCategoryId()); - - var initialModel = new HashMap<>(extensionManager.handleTicketEmailCustomText(event, ticketReservation, ticketReservationRepository.getAdditionalInfo(ticketReservation.getId()), ticketFieldRepository.findAllByTicketId(ticket.getId())) - .map(CustomEmailText::toMap) - .orElse(Map.of())); - if(EventUtil.isAccessOnline(ticketCategory, event)) { - initialModel.putAll(TicketCheckInUtil.getOnlineCheckInInfo( - extensionManager, - eventRepository, - ticketCategoryRepository, - configurationManager, - event, - ticketLanguage, - ticket, - ticketCategory, - additionalInfo - )); - } - var baseUrl = StringUtils.removeEnd(configurationManager.getFor(BASE_URL, ConfigurationLevel.event(event)).getRequiredValue(), "/"); - var calendarUrl = UriComponentsBuilder.fromUriString(baseUrl + "/api/v2/public/event/{eventShortName}/calendar/{currentLang}") - .queryParam("type", "google") - .build(Map.of("eventShortName", event.getShortName(), "currentLang", ticketLanguage.getLanguage())) - .toString(); - return TemplateProcessor.buildPartialEmail(event, organization, ticketReservation, ticketCategory, templateManager, baseUrl, ticketUrl, calendarUrl, ticketLanguage, initialModel).generate(ticket); - }; + return reservationHelper.getTicketEmailGenerator(event, ticketReservation, ticketLanguage, additionalInfo); } @Transactional @@ -1427,7 +1048,7 @@ public void cleanupExpiredReservations(Date expirationDate) { var toDelete = expiredReservationIds.stream() .filter(id -> !reservationsToIgnore.contains(id)) - .collect(toList()); + .toList(); subscriptionRepository.deleteSubscriptionWithReservationId(toDelete); specialPriceRepository.resetToFreeAndCleanupForReservation(toDelete); @@ -1443,7 +1064,7 @@ public void cleanupExpiredReservations(Date expirationDate) { .collect(Collectors.groupingBy(ReservationIdAndEventId::getEventId)); reservationIdsByEvent.forEach((eventId, reservations) -> { Event event = eventRepository.findById(eventId); - List<String> reservationIds = reservations.stream().map(ReservationIdAndEventId::getId).collect(toList()); + List<String> reservationIds = reservations.stream().map(ReservationIdAndEventId::getId).toList(); extensionManager.handleReservationsExpiredForEvent(event, reservationIds); billingDocumentRepository.deleteForReservations(reservationIds, eventId); transactionRepository.deleteForReservations(reservationIds); @@ -1463,7 +1084,7 @@ private void cleanupOfflinePayment(String reservationId) { Event event = eventRepository.findByReservationId(reservationId); boolean enabled = configurationManager.getFor(AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT, ConfigurationLevel.event(event)).getValueAsBooleanOrDefault(); if (enabled) { - deleteOfflinePayment(event, reservationId, true, false, null); + deleteOfflinePayment(event, reservationId, true, false, true, null); } else { log.trace("Will not cleanup reservation with id {} because the automatic removal has been disabled", reservationId); } @@ -1483,7 +1104,7 @@ private void cleanupOfflinePayment(String reservationId) { public void markExpiredInPaymentReservationAsStuck(Date expirationDate) { List<Pair<TicketReservation, Event>> stuckReservations = findStuckPaymentsToBeNotified(expirationDate); if(!stuckReservations.isEmpty()) { - List<String> ids = stuckReservations.stream().map(p -> p.getLeft().getId()).collect(toList()); + List<String> ids = stuckReservations.stream().map(p -> p.getLeft().getId()).toList(); ticketReservationRepository.updateReservationsStatus(ids, TicketReservationStatus.STUCK.name()); @@ -1496,7 +1117,7 @@ public void markExpiredInPaymentReservationAsStuck(Date expirationDate) { notificationManager.sendSimpleEmail(event, null, organization.getEmail(), STUCK_TICKETS_SUBJECT, () -> RenderedTemplate.plaintext(String.format(STUCK_TICKETS_MSG, event.getDisplayName()), Map.of())); - extensionManager.handleStuckReservations(event, reservations.stream().map(p -> p.getLeft().getId()).collect(toList())); + extensionManager.handleStuckReservations(event, reservations.stream().map(p -> p.getLeft().getId()).toList()); }); } } @@ -1532,35 +1153,7 @@ private List<Pair<TicketReservation, Event>> findStuckPaymentsToBeNotified(Date handlePaymentWebhookResult(event, providerAndWebhookResult.getLeft(), paymentWebhookResult, reservation, transaction, paymentContext, "stuck-check", false); return paymentWebhookResult.getType() == PaymentWebhookResult.Type.NOT_RELEVANT; }) - .collect(toList()); - } - - private static Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(PromoCodeDiscount promoCodeDiscount, - PurchaseContext purchaseContext, - TicketReservation reservation, - List<Ticket> tickets, - List<Pair<AdditionalService, List<AdditionalServiceItem>>> additionalServiceItems, - List<Subscription> subscriptions, - Optional<Subscription> appliedSubscription) { - - String currencyCode = purchaseContext.getCurrency(); - List<TicketPriceContainer> ticketPrices = tickets.stream().map(t -> TicketPriceContainer.from(t, reservation.getVatStatus(), purchaseContext.getVat(), purchaseContext.getVatStatus(), promoCodeDiscount)).collect(toList()); - int discountedTickets = (int) ticketPrices.stream().filter(t -> t.getAppliedDiscount().compareTo(BigDecimal.ZERO) > 0).count(); - int discountAppliedCount = discountedTickets <= 1 || promoCodeDiscount.getDiscountType() == DiscountType.FIXED_AMOUNT ? discountedTickets : 1; - if(discountAppliedCount == 0 && promoCodeDiscount != null && promoCodeDiscount.getDiscountType() == DiscountType.FIXED_AMOUNT_RESERVATION) { - discountAppliedCount = 1; - } - var reservationPriceCalculator = ReservationPriceCalculator.from(reservation, promoCodeDiscount, tickets, purchaseContext, additionalServiceItems, subscriptions, appliedSubscription); - var price = new TotalPrice(unitToCents(reservationPriceCalculator.getFinalPrice(), currencyCode), - unitToCents(reservationPriceCalculator.getVAT(), currencyCode), - -MonetaryUtil.unitToCents(reservationPriceCalculator.getAppliedDiscount(), currencyCode), - discountAppliedCount, - currencyCode); - return Pair.of(price, Optional.ofNullable(promoCodeDiscount)); - } - - private static Function<Pair<AdditionalService, List<AdditionalServiceItem>>, Stream<? extends AdditionalServiceItemPriceContainer>> generateASIPriceContainers(PurchaseContext purchaseContext, PromoCodeDiscount discount) { - return p -> p.getValue().stream().map(asi -> AdditionalServiceItemPriceContainer.from(asi, p.getKey(), purchaseContext, discount)); + .toList(); } /** @@ -1570,260 +1163,19 @@ private static Function<Pair<AdditionalService, List<AdditionalServiceItem>>, St * @return */ public Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(String reservationId) { - return totalReservationCostWithVAT(ticketReservationRepository.findReservationById(reservationId)); + return reservationCostCalculator.totalReservationCostWithVAT(reservationId); } public Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(TicketReservation reservation) { - return totalReservationCostWithVAT(purchaseContextManager.findByReservationId(reservation.getId()).orElseThrow(), reservation, ticketRepository.findTicketsInReservation(reservation.getId())); - } - - private Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(PurchaseContext purchaseContext, TicketReservation reservation, List<Ticket> tickets) { - var promoCodeDiscount = Optional.ofNullable(reservation.getPromoCodeDiscountId()).map(promoCodeDiscountRepository::findById); - var subscriptions = subscriptionRepository.findSubscriptionsByReservationId(reservation.getId()); - var appliedSubscription = subscriptionRepository.findAppliedSubscriptionByReservationId(reservation.getId()); - return totalReservationCostWithVAT(promoCodeDiscount.orElse(null), purchaseContext, reservation, tickets, - purchaseContext.event().map(event -> collectAdditionalServiceItems(reservation.getId(), event)).orElse(List.of()), - subscriptions, - appliedSubscription); - } - - private String formatPromoCode(PromoCodeDiscount promoCodeDiscount, List<Ticket> tickets, Locale locale, PurchaseContext purchaseContext) { - - if(promoCodeDiscount.getCodeType() == CodeType.DYNAMIC) { - return messageSourceManager.getMessageSourceFor(purchaseContext).getMessage("reservation.dynamic.discount.description", null, locale); //we don't expose the internal promo code - } - - List<Ticket> filteredTickets = tickets.stream().filter(ticket -> promoCodeDiscount.getCategories().contains(ticket.getCategoryId())).collect(toList()); - - if (promoCodeDiscount.getCategories().isEmpty() || filteredTickets.isEmpty()) { - return promoCodeDiscount.getPromoCode(); - } - - String formattedDiscountedCategories = filteredTickets.stream() - .map(Ticket::getCategoryId) - .collect(toSet()) - .stream() - .map(categoryId -> ticketCategoryRepository.getByIdAndActive(categoryId, promoCodeDiscount.getEventId()).getName()) - .collect(Collectors.joining(", ", "(", ")")); - - - return promoCodeDiscount.getPromoCode() + " " + formattedDiscountedCategories; + return reservationCostCalculator.totalReservationCostWithVAT(reservation); } public OrderSummary orderSummaryForReservationId(String reservationId, PurchaseContext purchaseContext) { - TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId); - return orderSummaryForReservation(reservation, purchaseContext); + return orderSummaryGenerator.orderSummaryForReservationId(reservationId, purchaseContext); } public OrderSummary orderSummaryForReservation(TicketReservation reservation, PurchaseContext context) { - var totalPriceAndDiscount = totalReservationCostWithVAT(reservation); - TotalPrice reservationCost = totalPriceAndDiscount.getLeft(); - PromoCodeDiscount discount = totalPriceAndDiscount.getRight().orElse(null); - // - boolean free = reservationCost.getPriceWithVAT() == 0; - String refundedAmount = null; - - boolean hasRefund = auditingRepository.countAuditsOfTypeForReservation(reservation.getId(), Audit.EventType.REFUND) > 0; - - if(hasRefund) { - refundedAmount = paymentManager.getInfo(reservation, context).getPaymentInformation().getRefundedAmount(); - } - - var currencyCode = reservation.getCurrencyCode(); - return new OrderSummary(reservationCost, - extractSummary(reservation.getId(), reservation.getVatStatus(), context, LocaleUtil.forLanguageTag(reservation.getUserLanguage()), discount, reservationCost), - free, - formatCents(reservationCost.getPriceWithVAT(), currencyCode), - formatCents(reservationCost.getVAT(), currencyCode), - reservation.getStatus() == TicketReservationStatus.OFFLINE_PAYMENT, - reservation.getStatus() == DEFERRED_OFFLINE_PAYMENT, - reservation.getPaymentMethod() == PaymentProxy.ON_SITE, - Optional.ofNullable(context.getVat()).map(p -> MonetaryUtil.formatCents(MonetaryUtil.unitToCents(p, currencyCode), currencyCode)).orElse(null), - reservation.getVatStatus(), - refundedAmount); - } - - private OrderSummary orderSummaryForCreditNote(TicketReservation reservation, PurchaseContext purchaseContext, List<Ticket> removedTickets) { - var totalPriceAndDiscount = totalReservationCostWithVAT(null, purchaseContext, reservation, removedTickets, List.of(), List.of(), Optional.empty()); - TotalPrice reservationCost = totalPriceAndDiscount.getLeft(); - // - boolean free = reservationCost.getPriceWithVAT() == 0; - - var currencyCode = reservation.getCurrencyCode(); - return new OrderSummary(reservationCost, - extractSummary(reservation.getVatStatus(), purchaseContext, LocaleUtil.forLanguageTag(reservation.getUserLanguage()), null, reservationCost, removedTickets, Stream.empty(), subscriptionRepository.findSubscriptionsByReservationId(reservation.getId())), - free, - formatCents(reservationCost.getPriceWithVAT(), currencyCode), - formatCents(reservationCost.getVAT(), currencyCode), - reservation.getStatus() == TicketReservationStatus.OFFLINE_PAYMENT, - reservation.getStatus() == DEFERRED_OFFLINE_PAYMENT, - reservation.getPaymentMethod() == PaymentProxy.ON_SITE, - Optional.ofNullable(purchaseContext.getVat()).map(p -> MonetaryUtil.formatCents(MonetaryUtil.unitToCents(p, currencyCode), currencyCode)).orElse(null), - reservation.getVatStatus(), - null); - } - - List<SummaryRow> extractSummary(VatStatus reservationVatStatus, - PurchaseContext purchaseContext, - Locale locale, - PromoCodeDiscount promoCodeDiscount, - TotalPrice reservationCost, - List<Ticket> ticketsToInclude, - Stream<Pair<AdditionalService, List<AdditionalServiceItem>>> additionalServicesToInclude, - List<Subscription> subscriptionsToInclude) { - log.trace("extract summary subscriptionsToInclude {}", subscriptionsToInclude); - List<SummaryRow> summary = new ArrayList<>(); - var currencyCode = reservationCost.getCurrencyCode(); - List<TicketPriceContainer> tickets = ticketsToInclude.stream() - .map(t -> TicketPriceContainer.from(t, reservationVatStatus, purchaseContext.getVat(), purchaseContext.getVatStatus(), promoCodeDiscount)).collect(toList()); - purchaseContext.event().ifPresent(event -> { - boolean multipleTaxRates = tickets.stream().map(TicketPriceContainer::getVatStatus).collect(Collectors.toSet()).size() > 1; - var ticketsByCategory = tickets.stream() - .collect(Collectors.groupingBy(TicketPriceContainer::getCategoryId)); - List<Entry<Integer, List<TicketPriceContainer>>> sorted; - if (multipleTaxRates) { - sorted = ticketsByCategory - .entrySet() - .stream() - .sorted(Comparator.comparing((Entry<Integer, List<TicketPriceContainer>> e) -> e.getValue().get(0).getVatStatus()).reversed()) - .collect(Collectors.toList()); - } else { - sorted = new ArrayList<>(ticketsByCategory.entrySet()); - } - Map<Integer, TicketCategory> categoriesById; - - if(ticketsByCategory.isEmpty()) { - categoriesById = Map.of(); - } else { - categoriesById = ticketCategoryRepository.getByIdsAndActive(ticketsByCategory.keySet(), event.getId()) - .stream() - .collect(Collectors.toMap(TicketCategory::getId, Function.identity())); - } - - for (var categoryWithTickets : sorted) { - var categoryTickets = categoryWithTickets.getValue(); - final int subTotal = categoryTickets.stream().mapToInt(TicketPriceContainer::getSummarySrcPriceCts).sum(); - final int subTotalBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(categoryTickets); - var firstTicket = categoryTickets.get(0); - final int ticketPriceCts = firstTicket.getSummarySrcPriceCts(); - final int priceBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(singletonList(firstTicket)); - String categoryName = categoriesById.get(categoryWithTickets.getKey()).getName(); - summary.add(new SummaryRow(categoryName, formatCents(ticketPriceCts, currencyCode), formatCents(priceBeforeVat, currencyCode), categoryTickets.size(), formatCents(subTotal, currencyCode), formatCents(subTotalBeforeVat, currencyCode), subTotal, SummaryType.TICKET, null)); - var ticketVatStatus = firstTicket.getVatStatus(); - if (VatStatus.isVatExempt(ticketVatStatus) && ticketVatStatus != reservationVatStatus) { - summary.add(new SummaryRow(null, - "", - "", - 0, - formatCents(0, currencyCode, true), - formatCents(0, currencyCode, true), - 0, - SummaryType.TAX_DETAIL, - "0")); - } - } - }); - - summary.addAll(additionalServicesToInclude - .map(entry -> { - String language = locale.getLanguage(); - AdditionalServiceText title = additionalServiceTextRepository.findBestMatchByLocaleAndType(entry.getKey().getId(), language, AdditionalServiceText.TextType.TITLE); - if(!title.getLocale().equals(language) || title.getId() == -1) { - log.debug("additional service {}: title not found for locale {}", title.getAdditionalServiceId(), language); - } - List<AdditionalServiceItemPriceContainer> prices = generateASIPriceContainers(purchaseContext, null).apply(entry).collect(toList()); - AdditionalServiceItemPriceContainer first = prices.get(0); - final int subtotal = prices.stream().mapToInt(AdditionalServiceItemPriceContainer::getSrcPriceCts).sum(); - final int subtotalBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(prices); - return new SummaryRow(title.getValue(), formatCents(first.getSrcPriceCts(), currencyCode), formatCents(SummaryPriceContainer.getSummaryPriceBeforeVatCts(singletonList(first)), currencyCode), prices.size(), formatCents(subtotal, currencyCode), formatCents(subtotalBeforeVat, currencyCode), subtotal, SummaryType.ADDITIONAL_SERVICE, null); - }).collect(toList())); - - Optional.ofNullable(promoCodeDiscount).ifPresent(promo -> { - String formattedSingleAmount = "-" + (DiscountType.isFixedAmount(promo.getDiscountType()) ? formatCents(promo.getDiscountAmount(), currencyCode) : (promo.getDiscountAmount()+"%")); - summary.add(new SummaryRow(formatPromoCode(promo, ticketsToInclude, locale, purchaseContext), - formattedSingleAmount, - formattedSingleAmount, - reservationCost.getDiscountAppliedCount(), - formatCents(reservationCost.getDiscount(), currencyCode), formatCents(reservationCost.getDiscount(), currencyCode), reservationCost.getDiscount(), - promo.isDynamic() ? SummaryType.DYNAMIC_DISCOUNT : SummaryType.PROMOTION_CODE, - null)); - }); - // - if(purchaseContext instanceof SubscriptionDescriptor) { - if(!subscriptionsToInclude.isEmpty()) { - var subscription = subscriptionsToInclude.get(0); - var priceContainer = new SubscriptionPriceContainer(subscription, promoCodeDiscount, (SubscriptionDescriptor) purchaseContext); - var priceBeforeVat = formatUnit(priceContainer.getNetPrice(), currencyCode); - summary.add(new SummaryRow(purchaseContext.getTitle().get(locale.getLanguage()), - formatCents(priceContainer.getSummarySrcPriceCts(), currencyCode), - priceBeforeVat, - subscriptionsToInclude.size(), - formatCents(priceContainer.getSummarySrcPriceCts() * subscriptionsToInclude.size(), currencyCode), - formatUnit(priceContainer.getNetPrice().multiply(new BigDecimal(subscriptionsToInclude.size())), currencyCode), - priceContainer.getSummarySrcPriceCts(), - SummaryType.SUBSCRIPTION, - null - )); - } - } else if(CollectionUtils.isNotEmpty(subscriptionsToInclude)) { - log.trace("subscriptions to include is not empty"); - var subscription = subscriptionsToInclude.get(0); - subscriptionRepository.findOne(subscription.getSubscriptionDescriptorId(), subscription.getOrganizationId()).ifPresent(subscriptionDescriptor -> { - log.trace("found subscriptionDescriptor with ID {}", subscriptionDescriptor.getId()); - // find tickets with subscription applied - var ticketsSubscription = tickets.stream().filter(t -> Objects.equals(subscription.getId(), t.getSubscriptionId())).collect(toList()); - final int ticketPriceCts = ticketsSubscription.stream().mapToInt(TicketPriceContainer::getSummarySrcPriceCts).sum(); - final int priceBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(ticketsSubscription); - summary.add(new SummaryRow(subscriptionDescriptor.getLocalizedTitle(locale), - "-" + formatCents(ticketPriceCts, currencyCode), - "-" + formatCents(priceBeforeVat, currencyCode), - ticketsSubscription.size(), - "-" + formatCents(ticketPriceCts, currencyCode), - "-" + formatCents(priceBeforeVat, currencyCode), - ticketPriceCts, - SummaryType.APPLIED_SUBSCRIPTION, - null - )); - }); - } - - // - return summary; - } - - List<SummaryRow> extractSummary(String reservationId, VatStatus reservationVatStatus, - PurchaseContext purchaseContext, Locale locale, PromoCodeDiscount promoCodeDiscount, TotalPrice reservationCost) { - List<Subscription> subscriptionsToInclude; - if(purchaseContext.ofType(PurchaseContextType.event)) { - subscriptionsToInclude = subscriptionRepository.findAppliedSubscriptionByReservationId(reservationId) - .map(List::of) - .orElse(List.of()); - } else { - subscriptionsToInclude = subscriptionRepository.findSubscriptionsByReservationId(reservationId); - } - - return extractSummary(reservationVatStatus, - purchaseContext, - locale, - promoCodeDiscount, - reservationCost, - ticketRepository.findTicketsInReservation(reservationId), - streamAdditionalServiceItems(reservationId, purchaseContext), - subscriptionsToInclude); - } - - private Stream<Pair<AdditionalService, List<AdditionalServiceItem>>> streamAdditionalServiceItems(String reservationId, PurchaseContext purchaseContext) { - return purchaseContext.event().map(event -> { - return additionalServiceItemRepository.findByReservationUuid(reservationId) - .stream() - .collect(Collectors.groupingBy(AdditionalServiceItem::getAdditionalServiceId)) - .entrySet() - .stream() - .map(entry -> Pair.of(additionalServiceRepository.getById(entry.getKey(), event.getId()), entry.getValue())); - }).orElse(Stream.empty()); - } - private List<Pair<AdditionalService, List<AdditionalServiceItem>>> collectAdditionalServiceItems(String reservationId, Event event) { - return streamAdditionalServiceItems(reservationId, event).collect(Collectors.toList()); + return orderSummaryGenerator.orderSummaryForReservation(reservation, context); } String reservationUrl(String reservationId) { @@ -1836,36 +1188,26 @@ public String reservationUrl(String reservationId, PurchaseContext purchaseConte return reservationUrl(ticketReservationRepository.findReservationById(reservationId), purchaseContext); } - public String reservationUrlForExternalClients(String reservationId, PurchaseContext purchaseContext, String userLanguage, boolean userLoggedIn) { + public String reservationUrlForExternalClients(String reservationId, PurchaseContext purchaseContext, String userLanguage, boolean userLoggedIn, String subscriptionId) { var configMap = configurationManager.getFor(EnumSet.of(BASE_URL, OPENID_PUBLIC_ENABLED), purchaseContext.getConfigurationLevel()); var baseUrl = StringUtils.removeEnd(configMap.get(BASE_URL).getRequiredValue(), "/"); if(userLoggedIn && configMap.get(OPENID_PUBLIC_ENABLED).getValueAsBooleanOrDefault()) { return baseUrl + "/openid/authentication?reservation=" + reservationId + "&contextType=" + purchaseContext.getType() + "&id=" + purchaseContext.getPublicIdentifier(); } else { - return reservationUrl(baseUrl, reservationId, purchaseContext, userLanguage); + var cleanSubscriptionId = StringUtils.trimToNull(subscriptionId); + return ReservationUtil.reservationUrl(baseUrl, reservationId, purchaseContext, userLanguage, cleanSubscriptionId != null ? "subscription="+cleanSubscriptionId : null); } } - String reservationUrl(String baseUrl, String reservationId, PurchaseContext purchaseContext, String userLanguage) { - return StringUtils.removeEnd(baseUrl, "/") + "/" + purchaseContext.getType()+ "/" + purchaseContext.getPublicIdentifier() + "/reservation/" + reservationId + "?lang="+userLanguage; - } - String reservationUrl(TicketReservation reservation, PurchaseContext purchaseContext) { - return reservationUrl(configurationManager.baseUrl(purchaseContext), reservation.getId(), purchaseContext, reservation.getUserLanguage()); + return ReservationUtil.reservationUrl(reservation, purchaseContext, configurationManager); } String ticketUrl(Event event, String ticketId) { Ticket ticket = ticketRepository.findByUUID(ticketId); - return configurationManager.baseUrl(event) + "/event/" + event.getShortName() + "/ticket/" + ticketId + "?lang=" + ticket.getUserLanguage(); } - public String ticketUpdateUrl(Event event, String ticketId) { - Ticket ticket = ticketRepository.findByUUID(ticketId); - - return configurationManager.baseUrl(event) + "/event/" + event.getShortName() + "/ticket/" + ticketId + "/update?lang=" + ticket.getUserLanguage(); - } - public String ticketOnlineCheckIn(Event event, String ticketId) { Ticket ticket = ticketRepository.findByUUID(ticketId); @@ -1922,11 +1264,11 @@ private void cancelReservation(TicketReservation reservation, boolean expired, S } - private void creditReservation(TicketReservation reservation, String username) { + private void creditReservation(TicketReservation reservation, String username, boolean sendEmail) { String reservationId = reservation.getId(); Event event = eventRepository.findByReservationId(reservationId); billingDocumentManager.ensureBillingDocumentIsPresent(event, reservation, username, () -> orderSummaryForReservationId(reservation.getId(), event)); - issueCreditNoteForReservation(event, reservation, username, true); + issueCreditNoteForReservation(event, reservation, username, sendEmail); cleanupReferencesToReservation(false, username, reservationId, event); extensionManager.handleReservationsCreditNoteIssuedForEvent(event, Collections.singletonList(reservationId)); } @@ -1982,10 +1324,6 @@ public Optional<Ticket> findFirstInReservation(String reservationId) { return ticketRepository.findFirstTicketInReservation(reservationId); } - public Optional<String> getVAT(PurchaseContext purchaseContext) { - return configurationManager.getFor(VAT_NR, purchaseContext.getConfigurationLevel()).getValue(); - } - public void updateTicketOwner(Ticket ticket, Locale locale, Event event, @@ -2016,7 +1354,7 @@ public void updateTicketOwner(Ticket ticket, boolean sendTicketAllowed = configurationManager.getFor(SEND_TICKETS_AUTOMATICALLY, ConfigurationLevel.event(event)).getValueAsBooleanOrDefault(); if (sendTicketAllowed && (newTicket.getStatus() == TicketStatus.ACQUIRED || newTicket.getStatus() == TicketStatus.TO_BE_PAID) && (!equalsIgnoreCase(newEmail, ticket.getEmail()) || !equalsIgnoreCase(customerName.getFullName(), ticket.getFullName()))) { - sendTicketByEmail(newTicket, userLocale, event, confirmationTextBuilder); + reservationHelper.sendTicketByEmail(newTicket, userLocale, event, confirmationTextBuilder); } boolean admin = isAdmin(userDetails); @@ -2040,12 +1378,14 @@ public void updateTicketOwner(Ticket ticket, } extensionManager.handleTicketAssignment(newTicket, ticketCategoryRepository.getById(ticket.getCategoryId()), updateTicketOwner.getAdditional()); - + if (isTicketBeingReassigned(ticket, updateTicketOwner, event)) { + invalidateAccess(event, ticket); + } Ticket postUpdateTicket = ticketRepository.findByUUID(ticket.getUuid()); Map<String, String> postUpdateTicketFields = ticketFieldRepository.findAllByTicketId(ticket.getId()).stream().collect(Collectors.toMap(TicketFieldValue::getName, TicketFieldValue::getValue)); - auditUpdateTicket(preUpdateTicket, preUpdateTicketFields, postUpdateTicket, postUpdateTicketFields, event.getId()); + auditingHelper.auditUpdateTicket(preUpdateTicket, preUpdateTicketFields, postUpdateTicket, postUpdateTicketFields, event.getId()); } boolean isTicketBeingReassigned(Ticket original, UpdateTicketOwnerForm updated, Event event) { @@ -2057,50 +1397,12 @@ boolean isTicketBeingReassigned(Ticket original, UpdateTicketOwnerForm updated, && (!equalsIgnoreCase(original.getEmail(), updated.getEmail()) || !equalsIgnoreCase(original.getFullName(), customerName.getFullName())); } - private void auditUpdateMetadata(String reservationId, - int ticketId, - int eventId, - TicketMetadataContainer newMetadata, - TicketMetadataContainer oldMetadata) { - List<Map<String, Object>> changes = ObjectDiffUtil.diff(oldMetadata, newMetadata, TicketMetadataContainer.class).stream() - .map(this::processChange) - .collect(Collectors.toList()); - - auditingRepository.insert(reservationId, null, eventId, Audit.EventType.UPDATE_TICKET_METADATA, new Date(), - TICKET, Integer.toString(ticketId), changes); - } - - private void auditUpdateTicket(Ticket preUpdateTicket, Map<String, String> preUpdateTicketFields, Ticket postUpdateTicket, Map<String, String> postUpdateTicketFields, int eventId) { - List<ObjectDiffUtil.Change> diffTicket = ObjectDiffUtil.diff(preUpdateTicket, postUpdateTicket); - List<ObjectDiffUtil.Change> diffTicketFields = ObjectDiffUtil.diff(preUpdateTicketFields, postUpdateTicketFields); - List<Map<String, Object>> changes = Stream.concat(diffTicket.stream(), diffTicketFields.stream()) - .map(this::processChange) - .collect(Collectors.toList()); - - auditingRepository.insert(preUpdateTicket.getTicketsReservationId(), null, eventId, - Audit.EventType.UPDATE_TICKET, new Date(), TICKET, Integer.toString(preUpdateTicket.getId()), changes); - } - - private HashMap<String, Object> processChange(ObjectDiffUtil.Change change) { - var v = new HashMap<String, Object>(); - v.put("propertyName", change.getPropertyName()); - v.put("state", change.getState()); - v.put("oldValue", change.getOldValue()); - v.put("newValue", change.getNewValue()); - return v; - } private boolean isAdmin(Optional<UserDetails> userDetails) { return userDetails.flatMap(u -> u.getAuthorities().stream().map(a -> Role.fromRoleName(a.getAuthority())).filter(Role.ADMIN::equals).findFirst()).isPresent(); } - void sendTicketByEmail(Ticket ticket, Locale locale, Event event, PartialTicketTextGenerator confirmationTextBuilder) { - TicketReservation reservation = ticketReservationRepository.findReservationById(ticket.getTicketsReservationId()); - TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()); - notificationManager.sendTicketByEmail(ticket, event, locale, confirmationTextBuilder, reservation, ticketCategory, () -> retrieveAttendeeAdditionalInfoForTicket(ticket)); - } - public Optional<Triple<Event, TicketReservation, Ticket>> fetchComplete(String eventName, String ticketIdentifier) { return ticketRepository.findOptionalByUUID(ticketIdentifier) .flatMap(ticket -> from(eventName, ticket.getTicketsReservationId(), ticketIdentifier) @@ -2130,7 +1432,7 @@ public Optional<Triple<Event, TicketReservation, Ticket>> fetchCompleteAndAssign }); } - public Optional<OnlineCheckInFullInfo> fetchCompleteAndAssignedForOnlineCheckIn(String eventName, String ticketIdentifier) { + public Optional<CheckInFullInfo> fetchCompleteAndAssignedForOnlineCheckIn(String eventName, String ticketIdentifier) { return ticketRepository.getFullInfoForOnlineCheckin(eventName, ticketIdentifier); } @@ -2152,11 +1454,11 @@ public void sendReminderForOfflinePayments() { .forEach(p -> { TicketReservation reservation = p.getLeft(); Event event = p.getMiddle(); - Map<String, Object> model = prepareModelForReservationEmail(event, reservation); + Map<String, Object> model = reservationHelper.prepareModelForReservationEmail(event, reservation); Locale locale = p.getRight(); ticketReservationRepository.flagAsOfflinePaymentReminderSent(reservation.getId()); notificationManager.sendSimpleEmail(event, reservation.getId(), reservation.getEmail(), messageSourceManager.getMessageSourceFor(event).getMessage("reservation.reminder.mail.subject", - new Object[]{getShortReservationID(event, reservation)}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_EMAIL, model, locale)); + new Object[]{configurationManager.getShortReservationID(event, reservation)}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_EMAIL, model, locale)); }); } @@ -2203,14 +1505,14 @@ private void sendOptionalDataReminder(Pair<Event, List<Ticket>> eventAndTickets) Event event = eventAndTickets.getLeft(); var messageSource = messageSourceManager.getMessageSourceFor(event); int daysBeforeStart = configurationManager.getFor(ASSIGNMENT_REMINDER_START, ConfigurationLevel.event(event)).getValueAsIntOrDefault(10); - List<Ticket> tickets = eventAndTickets.getRight().stream().filter(t -> !ticketFieldRepository.hasOptionalData(t.getId())).collect(toList()); + List<Ticket> tickets = eventAndTickets.getRight().stream().filter(t -> !ticketFieldRepository.hasOptionalData(t.getId())).toList(); Set<String> notYetNotifiedReservations = tickets.stream().map(Ticket::getTicketsReservationId).distinct().filter(rid -> findByIdForNotification(rid, clockProvider.withZone(event.getZoneId()), daysBeforeStart).isPresent()).collect(toSet()); tickets.stream() .filter(t -> notYetNotifiedReservations.contains(t.getTicketsReservationId())) .forEach(t -> { int result = ticketRepository.flagTicketAsReminderSent(t.getId()); Validate.isTrue(result == 1); - Map<String, Object> model = TemplateResource.prepareModelForReminderTicketAdditionalInfo(organizationRepository.getById(event.getOrganizationId()), event, t, ticketUpdateUrl(event, t.getUuid())); + Map<String, Object> model = TemplateResource.prepareModelForReminderTicketAdditionalInfo(organizationRepository.getById(event.getOrganizationId()), event, t, ReservationUtil.ticketUpdateUrl(event, t, configurationManager)); Locale locale = Optional.ofNullable(t.getUserLanguage()).map(LocaleUtil::forLanguageTag).orElseGet(() -> findReservationLanguage(t.getTicketsReservationId())); notificationManager.sendSimpleEmail(event, t.getTicketsReservationId(), t.getEmail(), messageSource.getMessage("reminder.ticket-additional-info.subject", new Object[]{event.getDisplayName()}, locale), () -> templateManager.renderTemplate(event, TemplateResource.REMINDER_TICKET_ADDITIONAL_INFO, model, locale)); @@ -2240,7 +1542,7 @@ private void sendAssignmentReminder(Pair<Event, Set<String>> p) { .filter(Optional::isPresent) .map(Optional::get) .forEach(reservation -> { - Map<String, Object> model = prepareModelForReservationEmail(event, reservation); + Map<String, Object> model = reservationHelper.prepareModelForReservationEmail(event, reservation); ticketReservationRepository.updateLatestReminderTimestamp(reservation.getId(), ZonedDateTime.now(clockProvider.withZone(eventZoneId))); Locale locale = findReservationLanguage(reservation.getId()); notificationManager.sendSimpleEmail(event, reservation.getId(), reservation.getEmail(), messageSource.getMessage("reminder.ticket-not-assigned.subject", @@ -2266,9 +1568,6 @@ public String getShortReservationID(Configurable event, String reservationId) { return configurationManager.getShortReservationID(event, findById(reservationId).orElseThrow()); } - public String getShortReservationID(Configurable event, TicketReservation reservation) { - return configurationManager.getShortReservationID(event, reservation); - } public int countAvailableTickets(EventAndOrganizationId event, TicketCategory category) { if(category.isBounded()) { @@ -2291,6 +1590,7 @@ public void releaseTicket(Event event, TicketReservation ticketReservation, fina throw new IllegalStateException("Cannot release reserved tickets"); } // + invalidateAccess(event, ticket); String reservationId = ticketReservation.getId(); //#365 - reset UUID when releasing a ticket @@ -2327,6 +1627,10 @@ public void releaseTicket(Event event, TicketReservation ticketReservation, fina extensionManager.handleTicketCancelledForEvent(event, Collections.singletonList(ticket.getUuid())); } + private void invalidateAccess(Event event, Ticket ticket) { + applicationEventPublisher.publishEvent(new InvalidateAccess(ticket, ticketRepository.getTicketMetadata(ticket.getId()), event)); + } + int getReservationTimeout(Configurable configurable) { return configurationManager.getFor(RESERVATION_TIMEOUT, configurable.getConfigurationLevel()).getValueAsIntOrDefault(25); } @@ -2336,9 +1640,9 @@ public void validateAndConfirmOfflinePayment(String reservationId, Event event, Optional<OrderSummary> optionalOrderSummary = optionally(() -> orderSummaryForReservationId(reservation.getId(), event)); Validate.isTrue(optionalOrderSummary.isPresent(), "Reservation not found"); OrderSummary orderSummary = optionalOrderSummary.orElseThrow(); - var currencyCode = orderSummary.getOriginalTotalPrice().getCurrencyCode(); - Validate.isTrue(MonetaryUtil.centsToUnit(orderSummary.getOriginalTotalPrice().getPriceWithVAT(), currencyCode).compareTo(paidAmount) == 0, "paid price differs from due price"); - confirmOfflinePayment(event, reservation.getId(), username); + var currencyCode = orderSummary.getOriginalTotalPrice().currencyCode(); + Validate.isTrue(MonetaryUtil.centsToUnit(orderSummary.getOriginalTotalPrice().priceWithVAT(), currencyCode).compareTo(paidAmount) == 0, "paid price differs from due price"); + confirmOfflinePayment(event, reservation.getId(), null, username); } public List<TicketReservationWithTransaction> getPendingPayments(String eventName) { @@ -2356,7 +1660,7 @@ public List<Pair<TicketReservation, BillingDocument>> findAllInvoices(int eventI Map<String, BillingDocument> documentsByReservationId = documents.stream().collect(toMap(BillingDocument::getReservationId, Function.identity())); return ticketReservationRepository.findByIds(documentsByReservationId.keySet()).stream() .map(r -> Pair.of(r, documentsByReservationId.get(r.getId()))) - .collect(toList()); + .toList(); } public Stream<Pair<TicketReservationWithTransaction, List<BillingDocument>>> streamAllDocumentsFor(int eventId) { @@ -2368,8 +1672,8 @@ public Stream<Pair<TicketReservationWithTransaction, List<BillingDocument>>> str .map(entry -> Pair.of(reservations.get(entry.getKey()), entry.getValue())); } - public Integer countInvoices(int eventId) { - return ticketReservationRepository.countInvoices(eventId); + public Integer countBillingDocuments(int eventId) { + return billingDocumentRepository.countAllForEvent(eventId); } @@ -2381,7 +1685,7 @@ public int revertTicketsToFreeIfAccessRestricted(int eventId) { List<Integer> restrictedCategories = ticketCategoryRepository.findAllTicketCategories(eventId).stream() .filter(TicketCategory::isAccessRestricted) .map(TicketCategory::getId) - .collect(toList()); + .toList(); if(!restrictedCategories.isEmpty()) { int count = ticketRepository.revertToFreeForRestrictedCategories(eventId, restrictedCategories); if(count > 0) { @@ -2462,6 +1766,10 @@ public void setReservationOwner(String reservationId, ); } + public void setReservationMetadata(String reservationId, ReservationMetadata metadata) { + Validate.isTrue(ticketReservationRepository.setMetadata(reservationId, metadata) == 1, "Error while updating metadata"); + } + static String buildCompleteBillingAddress(CustomerName customerName, String billingAddressCompany, String billingAddressLine1, @@ -2496,10 +1804,6 @@ public void updateReservationInvoicingAdditionalInformation(String reservationId ticketReservationRepository.updateInvoicingAdditionalInformation(reservationId, json.asJsonString(ticketReservationInvoicingAdditionalInfo)); } - private static Locale getReservationLocale(TicketReservation reservation) { - return StringUtils.isEmpty(reservation.getUserLanguage()) ? Locale.ENGLISH : LocaleUtil.forLanguageTag(reservation.getUserLanguage()); - } - public PaymentWebhookResult processTransactionWebhook(String body, String signature, PaymentProxy paymentProxy, Map<String, String> additionalInfo) { return processTransactionWebhook(body, signature, paymentProxy, additionalInfo, new PaymentContext()); } @@ -2570,36 +1874,31 @@ private PaymentWebhookResult handlePaymentWebhookResult(PurchaseContext purchase String operationType, boolean moveToWatingExternalConfirmationAllowed) { - switch(paymentWebhookResult.getType()) { - case NOT_RELEVANT: { - log.trace("Discarding event {} for reservation {}", operationType, reservation.getId()); - break; - } - case TRANSACTION_INITIATED: { - if(reservation.getStatus() == EXTERNAL_PROCESSING_PAYMENT && moveToWatingExternalConfirmationAllowed) { + switch (paymentWebhookResult.getType()) { + case NOT_RELEVANT -> log.trace("Discarding event {} for reservation {}", operationType, reservation.getId()); + case TRANSACTION_INITIATED -> { + if (reservation.getStatus() == EXTERNAL_PROCESSING_PAYMENT && moveToWatingExternalConfirmationAllowed) { String status = WAITING_EXTERNAL_CONFIRMATION.name(); log.trace("Event {} received. Setting status {} for reservation {}", operationType, status, reservation.getId()); ticketReservationRepository.updateReservationStatus(reservation.getId(), status); } else { log.trace("Ignoring Event {}, as it cannot be applied for reservation {} ({})", operationType, reservation.getId(), reservation.getStatus()); } - break; } - case SUCCESSFUL: { + case SUCCESSFUL -> { log.trace("Event {} for reservation {} has been successfully processed.", operationType, reservation.getId()); var totalPrice = totalReservationCostWithVAT(reservation).getLeft(); var paymentToken = paymentWebhookResult.getPaymentToken(); var paymentSpecification = new PaymentSpecification(reservation, totalPrice, purchaseContext, paymentToken, - orderSummaryForReservation(reservation, purchaseContext), true, eventHasPrivacyPolicy(purchaseContext)); - transitionToComplete(paymentSpecification, totalPrice, paymentToken.getPaymentProvider(), null); - break; + orderSummaryForReservation(reservation, purchaseContext), true, hasPrivacyPolicy(purchaseContext)); + transitionToComplete(paymentSpecification, paymentToken.getPaymentProvider(), null); } - case FAILED: { + case FAILED -> { // depending on when we actually receive the event, we could have two possibilities: // // 1) the user is still waiting on the payment page. In this case, there's no harm in reverting the reservation status to PENDING - // 2) the user has given up and we're officially in background mode. + // 2) the user has given up, and we're officially in background mode. // // either way, we have to notify the user about the charge failure. Then: // @@ -2612,25 +1911,23 @@ private PaymentWebhookResult handlePaymentWebhookResult(PurchaseContext purchase Date now = new Date(); int slackTime = configurationManager.getFor(RESERVATION_MIN_TIMEOUT_AFTER_FAILED_PAYMENT, paymentContext.getConfigurationLevel()).getValueAsIntOrDefault(10); PaymentMethod paymentMethodForTransaction = paymentProvider.getPaymentMethodForTransaction(transaction); - if(expiration.before(now)) { + if (expiration.before(now)) { sendTransactionFailedEmail(purchaseContext, reservation, paymentMethodForTransaction, paymentWebhookResult, true); cancelReservation(reservation, false, null); break; - } else if(DateUtils.addMinutes(expiration, -slackTime).before(now)) { + } else if (DateUtils.addMinutes(expiration, -slackTime).before(now)) { ticketReservationRepository.updateValidity(reservation.getId(), DateUtils.addMinutes(now, slackTime)); } reTransitionToPending(reservation.getId(), false); purchaseContext.event().ifPresent(event -> sendTransactionFailedEmail(event, reservation, paymentMethodForTransaction, paymentWebhookResult, false)); - break; } - case CANCELLED: { + case CANCELLED -> { reTransitionToPending(reservation.getId(), false); log.debug("Event {} for reservation {} has been cancelled", operationType, reservation.getId()); - break; } - default: + default -> { // do nothing for ERROR/REJECTED - break; + } } return paymentWebhookResult; } @@ -2647,21 +1944,16 @@ public Optional<PaymentResult> forceTransactionCheck(PurchaseContext purchaseCon var paymentWebhookResult = providerAndWebhookResult.getRight(); handlePaymentWebhookResult(purchaseContext, providerAndWebhookResult.getLeft(), paymentWebhookResult, reservation, transaction, paymentContext, "force-check", true); - switch(paymentWebhookResult.getType()) { - case FAILED: - case REJECTED: - case CANCELLED: - return PaymentResult.failed(paymentWebhookResult.getReason()); - case NOT_RELEVANT: - case ERROR: + return switch (paymentWebhookResult.getType()) { + case FAILED, REJECTED, CANCELLED -> PaymentResult.failed(paymentWebhookResult.getReason()); + case NOT_RELEVANT, ERROR -> // to be on the safe side, we ignore errors when trying to reload the payment // because they could be caused by network/availability problems - return PaymentResult.pending(transaction.getPaymentId()); - case TRANSACTION_INITIATED: - return StringUtils.isNotEmpty(paymentWebhookResult.getRedirectUrl()) ? PaymentResult.redirect(paymentWebhookResult.getRedirectUrl()) : PaymentResult.pending(transaction.getPaymentId()); - default: - return PaymentResult.successful(paymentWebhookResult.getPaymentToken().getToken()); - } + PaymentResult.pending(transaction.getPaymentId()); + case TRANSACTION_INITIATED -> + StringUtils.isNotEmpty(paymentWebhookResult.getRedirectUrl()) ? PaymentResult.redirect(paymentWebhookResult.getRedirectUrl()) : PaymentResult.pending(transaction.getPaymentId()); + default -> PaymentResult.successful(paymentWebhookResult.getPaymentToken().getToken()); + }; }); } @@ -2683,7 +1975,7 @@ private boolean reservationStatusNotCompatible(TicketReservation reservation) { } private void sendTransactionFailedEmail(PurchaseContext purchaseContext, TicketReservation reservation, PaymentMethod paymentMethod, PaymentWebhookResult paymentWebhookResult, boolean cancelReservation) { - var shortReservationID = getShortReservationID(purchaseContext, reservation); + var shortReservationID = configurationManager.getShortReservationID(purchaseContext, reservation); var messageSource = messageSourceManager.getMessageSourceFor(purchaseContext); Map<String, Object> model = Map.of( ORGANIZATION, organizationRepository.getById(purchaseContext.getOrganizationId()), @@ -2691,7 +1983,7 @@ private void sendTransactionFailedEmail(PurchaseContext purchaseContext, TicketR "reservation", reservation, RESERVATION_ID, shortReservationID, "eventName", purchaseContext.getDisplayName(), - "provider", requireNonNullElse(paymentMethod.name(), ""), + "provider", requireNonNullElse(paymentMethod, PaymentMethod.NONE).name(), "reason", paymentWebhookResult.getReason(), "reservationUrl", reservationUrl(reservation, purchaseContext)); @@ -2759,9 +2051,17 @@ public Optional<String> createSubscriptionReservation(SubscriptionDescriptor sub Locale locale, BindingResult bindingResult, Principal principal) { + return createSubscriptionReservation(subscriptionDescriptor, locale, bindingResult, principal, SubscriptionMetadata.empty()); + } + + public Optional<String> createSubscriptionReservation(SubscriptionDescriptor subscriptionDescriptor, + Locale locale, + BindingResult bindingResult, + Principal principal, + SubscriptionMetadata metadata) { Date expiration = DateUtils.addMinutes(new Date(), getReservationTimeout(subscriptionDescriptor)); try { - return Optional.of(createSubscriptionReservation(subscriptionDescriptor, expiration, locale, retrievePublicUserId(principal))); + return Optional.of(createSubscriptionReservation(subscriptionDescriptor, expiration, locale, retrievePublicUserId(principal), metadata)); } catch (CannotProceedWithPayment cannotProceedWithPayment) { bindingResult.reject("error.STEP_1_PAYMENT_METHODS_ERROR"); log.error("missing payment methods", cannotProceedWithPayment); @@ -2790,13 +2090,13 @@ public Optional<String> createTicketReservation(Event event, false, principal); return Optional.of(reservationId); - } catch (TicketReservationManager.NotEnoughTicketsException nete) { + } catch (NotEnoughTicketsException nete) { bindingResult.reject(ErrorsCode.STEP_1_NOT_ENOUGH_TICKETS); - } catch (TicketReservationManager.MissingSpecialPriceTokenException missing) { + } catch (MissingSpecialPriceTokenException missing) { bindingResult.reject(ErrorsCode.STEP_1_ACCESS_RESTRICTED); - } catch (TicketReservationManager.InvalidSpecialPriceTokenException invalid) { + } catch (InvalidSpecialPriceTokenException invalid) { bindingResult.reject(ErrorsCode.STEP_1_CODE_NOT_FOUND); - } catch (TicketReservationManager.TooManyTicketsForDiscountCodeException tooMany) { + } catch (TooManyTicketsForDiscountCodeException tooMany) { bindingResult.reject(ErrorsCode.STEP_2_DISCOUNT_CODE_USAGE_EXCEEDED); } catch (CannotProceedWithPayment cannotProceedWithPayment) { bindingResult.reject(ErrorsCode.STEP_1_CATEGORIES_NOT_COMPATIBLE); @@ -2812,7 +2112,7 @@ boolean canProceedWithPayment(PurchaseContext purchaseContext, TotalPrice totalP var categoriesInReservation = ticketRepository.getCategoriesIdToPayInReservation(reservationId); var blacklistedPaymentMethods = configurationManager.getBlacklistedMethodsForReservation(purchaseContext, categoriesInReservation); var transactionRequest = new TransactionRequest(totalPrice, ticketReservationRepository.getBillingDetailsForReservation(reservationId)); - var availableMethods = paymentManager.getPaymentMethods(purchaseContext, transactionRequest).stream().filter(pm -> pm.getStatus() == PaymentMethodStatus.ACTIVE && pm.getPaymentMethod() != PaymentMethod.NONE).collect(toList()); + var availableMethods = paymentManager.getPaymentMethods(purchaseContext, transactionRequest).stream().filter(pm -> pm.getStatus() == PaymentMethodStatus.ACTIVE && pm.getPaymentMethod() != PaymentMethod.NONE).toList(); if(availableMethods.isEmpty() || availableMethods.stream().allMatch(pm -> blacklistedPaymentMethods.contains(pm.getPaymentMethod()))) { log.error("Cannot proceed with reservation. No payment methods available {} or all blacklisted {}", availableMethods, blacklistedPaymentMethods); return false; @@ -2844,8 +2144,7 @@ public void flagAsValidated(String reservationId, PurchaseContext purchaseContex private void checkOfflinePaymentsForEvent(Event event) { log.trace("check offline payments for event {}", event.getShortName()); var paymentContext = new PaymentContext(event); - var providers = paymentManager.streamActiveProvidersByProxyAndCapabilities(PaymentProxy.OFFLINE, paymentContext, List.of(OfflineProcessor.class)) - .collect(toList()); + var providers = paymentManager.streamActiveProvidersByProxyAndCapabilities(PaymentProxy.OFFLINE, paymentContext, List.of(OfflineProcessor.class)).toList(); if(providers.isEmpty()) { log.trace("No active offline provider has been found. Exiting..."); return; @@ -2919,12 +2218,12 @@ private void processResults(Event event, Map<String, TicketReservationWithTransa } }); - pendingReview.addAll(byStatus.getOrDefault(Transaction.Status.OFFLINE_PENDING_REVIEW, List.of()).stream().map(tr -> tr.getTicketReservation().getId()).collect(toList())); + pendingReview.addAll(byStatus.getOrDefault(Transaction.Status.OFFLINE_PENDING_REVIEW, List.of()).stream().map(tr -> tr.getTicketReservation().getId()).toList()); } private boolean automaticConfirmOfflinePayment(Event event, String reservationId) { try { - confirmOfflinePayment(event, reservationId, null); + confirmOfflinePayment(event, reservationId, null, null); return true; } catch(Exception ex) { log.warn("Unable to confirm reservation "+reservationId, ex); @@ -3057,15 +2356,15 @@ boolean applySubscriptionCode(int eventId, var purchaseContext = purchaseContextManager.findByReservationId(reservation.getId()).orElseThrow(); ticketReservationRepository.updateBillingData(reservation.getVatStatus(), calculateSrcPrice(purchaseContext.getVatStatus(), totalPrice), - totalPrice.getPriceWithVAT(), - totalPrice.getVAT(), - Math.abs(totalPrice.getDiscount()), + totalPrice.priceWithVAT(), + totalPrice.VAT(), + Math.abs(totalPrice.discount()), purchaseContext.getCurrency(), reservation.getVatNr(), reservation.getVatCountryCode(), reservation.isInvoiceRequested(), reservation.getId()); - log.trace("subscription applied. totalPrice is {}", totalPrice.getPriceWithVAT()); + log.trace("subscription applied. totalPrice is {}", totalPrice.priceWithVAT()); return true; } @@ -3116,9 +2415,7 @@ public Optional<SubscriptionWithUsageDetails> findSubscriptionDetails(TicketRese } public Map<String, List<String>> retrieveAttendeeAdditionalInfoForTicket(Ticket ticket) { - return ticketFieldRepository.findNameAndValue(ticket.getId()) - .stream() - .collect(groupingBy(FieldNameAndValue::getName, mapping(FieldNameAndValue::getValue, toList()))); + return reservationHelper.retrieveAttendeeAdditionalInfoForTicket(ticket); } private Integer retrievePublicUserId(Principal principal) { diff --git a/src/main/java/alfio/manager/UploadedResourceManager.java b/src/main/java/alfio/manager/UploadedResourceManager.java index c31b936979..22a9e593e8 100644 --- a/src/main/java/alfio/manager/UploadedResourceManager.java +++ b/src/main/java/alfio/manager/UploadedResourceManager.java @@ -19,8 +19,9 @@ import alfio.model.UploadedResource; import alfio.model.modification.UploadBase64FileModification; import alfio.repository.UploadedResourceRepository; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -38,9 +39,10 @@ @Component @Transactional -@Log4j2 public class UploadedResourceManager { + private static final Logger log = LoggerFactory.getLogger(UploadedResourceManager.class); + public static final String ATTR_IMG_WIDTH = "width"; public static final String ATTR_IMG_HEIGHT = "height"; @@ -93,7 +95,7 @@ public Optional<Integer> saveResource(UploadBase64FileModification file) { uploadedResourceRepository.delete(file.getName()); } - return Optional.ofNullable(uploadedResourceRepository.upload(null, null, file, getAttributes(file))); + return Optional.of(uploadedResourceRepository.upload(null, null, file, getAttributes(file))); } public Optional<Integer> saveResource(int organizationId, UploadBase64FileModification file) { @@ -101,7 +103,7 @@ public Optional<Integer> saveResource(int organizationId, UploadBase64FileModifi uploadedResourceRepository.delete(organizationId, file.getName()); } - return Optional.ofNullable(uploadedResourceRepository.upload(organizationId, null, file, getAttributes(file))); + return Optional.of(uploadedResourceRepository.upload(organizationId, null, file, getAttributes(file))); } public Optional<Integer> saveResource(int organizationId, int eventId, UploadBase64FileModification file) { diff --git a/src/main/java/alfio/manager/WaitingQueueManager.java b/src/main/java/alfio/manager/WaitingQueueManager.java index 5d58752e29..94cfe807bf 100644 --- a/src/main/java/alfio/manager/WaitingQueueManager.java +++ b/src/main/java/alfio/manager/WaitingQueueManager.java @@ -30,11 +30,11 @@ import alfio.repository.user.OrganizationRepository; import alfio.util.*; import ch.digitalfondue.npjt.AffectedRowCountAndKey; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Component; @@ -48,10 +48,10 @@ import static alfio.util.EventUtil.determineAvailableSeats; @Component -@Log4j2 -@AllArgsConstructor public class WaitingQueueManager { + private static final Logger log = LoggerFactory.getLogger(WaitingQueueManager.class); + private final WaitingQueueRepository waitingQueueRepository; private final TicketRepository ticketRepository; private final TicketCategoryRepository ticketCategoryRepository; @@ -65,6 +65,32 @@ public class WaitingQueueManager { private final ExtensionManager extensionManager; private final ClockProvider clockProvider; + public WaitingQueueManager(WaitingQueueRepository waitingQueueRepository, + TicketRepository ticketRepository, + TicketCategoryRepository ticketCategoryRepository, + ConfigurationManager configurationManager, + EventStatisticsManager eventStatisticsManager, + NotificationManager notificationManager, + TemplateManager templateManager, + MessageSourceManager messageSourceManager, + OrganizationRepository organizationRepository, + EventRepository eventRepository, + ExtensionManager extensionManager, + ClockProvider clockProvider) { + this.waitingQueueRepository = waitingQueueRepository; + this.ticketRepository = ticketRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.configurationManager = configurationManager; + this.eventStatisticsManager = eventStatisticsManager; + this.notificationManager = notificationManager; + this.templateManager = templateManager; + this.messageSourceManager = messageSourceManager; + this.organizationRepository = organizationRepository; + this.eventRepository = eventRepository; + this.extensionManager = extensionManager; + this.clockProvider = clockProvider; + } + public boolean subscribe(Event event, CustomerName customerName, String email, Integer selectedCategoryId, Locale userLanguage) { try { if(configurationManager.getFor(STOP_WAITING_QUEUE_SUBSCRIPTIONS, ConfigurationLevel.event(event)).getValueAsBooleanOrDefault()) { diff --git a/src/main/java/alfio/manager/WaitingQueueSubscriptionProcessor.java b/src/main/java/alfio/manager/WaitingQueueSubscriptionProcessor.java index d37e23ac0c..1bf07cc9c6 100644 --- a/src/main/java/alfio/manager/WaitingQueueSubscriptionProcessor.java +++ b/src/main/java/alfio/manager/WaitingQueueSubscriptionProcessor.java @@ -29,9 +29,9 @@ import alfio.util.ClockProvider; import alfio.util.TemplateManager; import alfio.util.TemplateResource; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; @@ -51,10 +51,10 @@ @Component @Transactional -@RequiredArgsConstructor -@Log4j2 public class WaitingQueueSubscriptionProcessor { + private static final Logger log = LoggerFactory.getLogger(WaitingQueueSubscriptionProcessor.class); + private final EventManager eventManager; private final TicketReservationManager ticketReservationManager; private final ConfigurationManager configurationManager; @@ -67,6 +67,30 @@ public class WaitingQueueSubscriptionProcessor { private final PlatformTransactionManager transactionManager; private final ClockProvider clockProvider; + public WaitingQueueSubscriptionProcessor(EventManager eventManager, + TicketReservationManager ticketReservationManager, + ConfigurationManager configurationManager, + WaitingQueueManager waitingQueueManager, + NotificationManager notificationManager, + WaitingQueueRepository waitingQueueRepository, + MessageSourceManager messageSourceManager, + TemplateManager templateManager, + TicketRepository ticketRepository, + PlatformTransactionManager transactionManager, + ClockProvider clockProvider) { + this.eventManager = eventManager; + this.ticketReservationManager = ticketReservationManager; + this.configurationManager = configurationManager; + this.waitingQueueManager = waitingQueueManager; + this.notificationManager = notificationManager; + this.waitingQueueRepository = waitingQueueRepository; + this.messageSourceManager = messageSourceManager; + this.templateManager = templateManager; + this.ticketRepository = ticketRepository; + this.transactionManager = transactionManager; + this.clockProvider = clockProvider; + } + public void handleWaitingTickets() { Map<Boolean, List<Event>> activeEvents = eventManager.getActiveEvents().stream() .collect(Collectors.partitioningBy(this::isWaitingListFormEnabled)); diff --git a/src/main/java/alfio/manager/i18n/MessageSourceManager.java b/src/main/java/alfio/manager/i18n/MessageSourceManager.java index 549dc5d4a5..b661972bd2 100644 --- a/src/main/java/alfio/manager/i18n/MessageSourceManager.java +++ b/src/main/java/alfio/manager/i18n/MessageSourceManager.java @@ -16,12 +16,10 @@ */ package alfio.manager.i18n; -import alfio.model.Event; import alfio.model.PurchaseContext; import alfio.repository.system.ConfigurationRepository; import alfio.util.CustomResourceBundleMessageSource; import alfio.util.LocaleUtil; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; import org.springframework.context.MessageSource; import org.springframework.context.support.AbstractMessageSource; @@ -32,12 +30,21 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; -@Log4j2 public class MessageSourceManager { + /** + * No-op filter used to load all keys in the resource bundle, to be used with the new Admin frontend + */ + public static final Predicate<String> ADMIN_FRONTEND = key -> true; + /** + * Filters out all the "admin" keys which would not make sense to load on the public frontend + */ + public static final Predicate<String> PUBLIC_FRONTEND = key -> !key.startsWith("admin."); + private static final Pattern ARGUMENT_FINDER = Pattern.compile("\\{+(\\d+)}+"); private final CustomResourceBundleMessageSource messageSource; private final ConfigurationRepository configurationRepository; @@ -100,12 +107,16 @@ private static Map<String, String> convertPlaceholders(Map<String, String> bundl return res; } - public Map<String, String> getBundleAsMap(String baseName, boolean withSystemOverride, String lang) { + public Map<String, String> getBundleAsMap(String baseName, + boolean withSystemOverride, + String lang, + Predicate<String> keysFilter) { var locale = LocaleUtil.forLanguageTag(lang); - var messageSource = getRootMessageSource(withSystemOverride); + var rootMessageSource = getRootMessageSource(withSystemOverride); return getKeys(baseName, locale) .stream() - .collect(Collectors.toMap(Function.identity(), k -> convertPlaceholder(messageSource.getMessage(k, EMPTY_ARRAY, locale)))); + .filter(keysFilter) + .collect(Collectors.toMap(Function.identity(), k -> convertPlaceholder(rootMessageSource.getMessage(k, EMPTY_ARRAY, locale)))); } private static class MessageSourceWithOverride extends AbstractMessageSource { diff --git a/src/main/java/alfio/manager/openid/AdminOpenIdAuthenticationManager.java b/src/main/java/alfio/manager/openid/AdminOpenIdAuthenticationManager.java index bdfb7784ff..d6eadfce8e 100644 --- a/src/main/java/alfio/manager/openid/AdminOpenIdAuthenticationManager.java +++ b/src/main/java/alfio/manager/openid/AdminOpenIdAuthenticationManager.java @@ -27,8 +27,7 @@ import alfio.repository.user.join.UserOrganizationRepository; import alfio.util.Json; import com.auth0.jwt.interfaces.Claim; -import lombok.SneakyThrows; -import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.concurrent.ConcurrentException; import org.apache.commons.lang3.concurrent.LazyInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,12 +38,11 @@ import java.net.http.HttpClient; import java.util.*; import java.util.regex.Pattern; -import java.util.stream.Collectors; -@Log4j2 public class AdminOpenIdAuthenticationManager extends BaseOpenIdAuthenticationManager { - private final Logger logger = LoggerFactory.getLogger(AdminOpenIdAuthenticationManager.class); + private static final Logger log = LoggerFactory.getLogger(AdminOpenIdAuthenticationManager.class); + private static final String ALFIO_ADMIN = "ALFIO_ADMIN"; private final LazyConfigurationContainer configurationContainer; @@ -74,9 +72,9 @@ public AdminOpenIdAuthenticationManager(Environment environment, @Override protected OpenIdAlfioUser fromToken(String idToken, String subject, String email, Map<String, Claim> idTokenClaims) { - List<String> groupsList = idTokenClaims.get(openIdConfiguration().getRolesParameter()).asList(String.class); + List<String> groupsList = idTokenClaims.get(openIdConfiguration().rolesParameter()).asList(String.class); log.trace("IdToken contains the following groups: {}", groupsList); - List<String> groups = groupsList.stream().filter(group -> group.startsWith("ALFIO_")).collect(Collectors.toList()); + List<String> groups = groupsList.stream().filter(group -> group.startsWith("ALFIO_")).toList(); boolean isAdmin = groups.contains(ALFIO_ADMIN); if (isAdmin) { @@ -88,21 +86,25 @@ protected OpenIdAlfioUser fromToken(String idToken, String subject, String email if(groups.isEmpty()){ String message = "Users must have at least a group called ALFIO_ADMIN or ALFIO_BACKOFFICE"; - logger.error(message); + log.error(message); throw new RuntimeException(message); } - List<String> alfioOrganizationAuthorizationsRaw = idTokenClaims.get(openIdConfiguration().getAlfioGroupsParameter()).asList(String.class); + List<String> alfioOrganizationAuthorizationsRaw = idTokenClaims.get(openIdConfiguration().alfioGroupsParameter()).asList(String.class); log.trace("IdToken contains the following alfioGroups: {}", alfioOrganizationAuthorizationsRaw); Map<String, Set<String>> alfioOrganizationAuthorizations = extractOrganizationRoles(alfioOrganizationAuthorizationsRaw); Set<Role> alfioRoles = extractAlfioRoles(alfioOrganizationAuthorizations); return new OpenIdAlfioUser(idToken, subject, email, getUserType(), alfioRoles, alfioOrganizationAuthorizations); } - @SneakyThrows + @Override protected OpenIdConfiguration openIdConfiguration() { - return configurationContainer.get(); + try { + return configurationContainer.get(); + } catch (ConcurrentException e) { + throw new IllegalStateException(e); + } } private Set<Role> extractAlfioRoles(Map<String, Set<String>> alfioOrganizationAuthorizations) { @@ -122,10 +124,10 @@ public List<String> getScopes() { "openid", "email", "profile", - openIdConfiguration.getRolesParameter(), - openIdConfiguration.getAlfioGroupsParameter(), - openIdConfiguration.getGivenNameClaim(), - openIdConfiguration.getFamilyNameClaim() + openIdConfiguration.rolesParameter(), + openIdConfiguration.alfioGroupsParameter(), + openIdConfiguration.givenNameClaim(), + openIdConfiguration.familyNameClaim() ); } @@ -178,6 +180,5 @@ protected OpenIdConfiguration initialize() { return OpenIdConfiguration.from(environment, configurationManager); } } - } diff --git a/src/main/java/alfio/manager/openid/BaseOpenIdAuthenticationManager.java b/src/main/java/alfio/manager/openid/BaseOpenIdAuthenticationManager.java index 51c402827f..7f2a92876f 100644 --- a/src/main/java/alfio/manager/openid/BaseOpenIdAuthenticationManager.java +++ b/src/main/java/alfio/manager/openid/BaseOpenIdAuthenticationManager.java @@ -32,8 +32,9 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.Claim; import com.fasterxml.jackson.core.type.TypeReference; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.security.core.GrantedAuthority; @@ -53,8 +54,10 @@ import static alfio.util.HttpUtils.APPLICATION_FORM_URLENCODED; import static alfio.util.HttpUtils.APPLICATION_JSON; -@Log4j2 abstract class BaseOpenIdAuthenticationManager implements OpenIdAuthenticationManager { + + private static final Logger log = LoggerFactory.getLogger(OpenIdAuthenticationManager.class); + protected static final String CODE = "code"; protected static final String ID_TOKEN = "id_token"; protected static final String SUBJECT = "sub"; @@ -126,8 +129,8 @@ private OpenIdAlfioAuthentication createOrRetrieveUser(OpenIdAlfioUser user, var configuration = openIdConfiguration(); var result = userRepository.create(user.getEmail(), passwordEncoder.encode(PasswordGenerator.generateRandomPassword()), - retrieveClaimOrBlank(idTokenClaims, configuration.getGivenNameClaim()), - retrieveClaimOrBlank(idTokenClaims, configuration.getFamilyNameClaim()), + retrieveClaimOrBlank(idTokenClaims, configuration.givenNameClaim()), + retrieveClaimOrBlank(idTokenClaims, configuration.familyNameClaim()), user.getEmail(), true, getUserType(), @@ -217,15 +220,15 @@ private void updateRoles(Set<Role> roles, String username) { @Override public String buildAuthorizeUrl(String state) { - log.trace("buildAuthorizeUrl, configuration: {}", this::openIdConfiguration); + log.trace("buildAuthorizeUrl, configuration: {}", openIdConfiguration()); String scopeParameter = String.join("+", getScopes()); UriComponents uri = UriComponentsBuilder.newInstance() .scheme(HTTPS) - .host(openIdConfiguration().getDomain()) - .path(openIdConfiguration().getAuthenticationUrl()) - .queryParam(REDIRECT_URI, openIdConfiguration().getCallbackURI()) - .queryParam("client_id", openIdConfiguration().getClientId()) + .host(openIdConfiguration().domain()) + .path(openIdConfiguration().authenticationUrl()) + .queryParam(REDIRECT_URI, openIdConfiguration().callbackURI()) + .queryParam("client_id", openIdConfiguration().clientId()) .queryParam("state", state) .queryParam("scope", scopeParameter) .queryParam("response_type", "code") @@ -237,8 +240,8 @@ public String buildAuthorizeUrl(String state) { public String buildClaimsRetrieverUrl() { UriComponents uri = UriComponentsBuilder.newInstance() .scheme(HTTPS) - .host(openIdConfiguration().getDomain()) - .path(openIdConfiguration().getTokenEndpoint()) + .host(openIdConfiguration().domain()) + .path(openIdConfiguration().tokenEndpoint()) .build(); return uri.toUriString(); } @@ -247,16 +250,16 @@ public String buildClaimsRetrieverUrl() { public String buildLogoutUrl() { UriComponents uri = UriComponentsBuilder.newInstance() .scheme(HTTPS) - .host(openIdConfiguration().getDomain()) - .path(openIdConfiguration().getLogoutUrl()) - .queryParam(REDIRECT_URI, openIdConfiguration().getLogoutRedirectUrl()) + .host(openIdConfiguration().domain()) + .path(openIdConfiguration().logoutUrl()) + .queryParam(REDIRECT_URI, openIdConfiguration().logoutRedirectUrl()) .build(); return uri.toString(); } @Override public String buildRetrieveClaimsUrlBody(String code) { - var contentType = openIdConfiguration().getContentType(); + var contentType = openIdConfiguration().contentType(); if (contentType.equals(APPLICATION_JSON)) { return buildAccessTokenUrlJson(code); } @@ -270,9 +273,9 @@ private String buildAccessTokenUrlJson(String code) { Map<String, String> body = Map.of( "grant_type", "authorization_code", "code", code, - "client_id", openIdConfiguration().getClientId(), - "client_secret", openIdConfiguration().getClientSecret(), - REDIRECT_URI, openIdConfiguration().getCallbackURI() + "client_id", openIdConfiguration().clientId(), + "client_secret", openIdConfiguration().clientSecret(), + REDIRECT_URI, openIdConfiguration().callbackURI() ); return json.asJsonString(body); } @@ -280,16 +283,16 @@ REDIRECT_URI, openIdConfiguration().getCallbackURI() private String buildAccessTokenUrlForm(String code) { return "grant_type=authorization_code" + "&code=" + code + - "&client_id=" + openIdConfiguration().getClientId() + - "&client_secret=" + openIdConfiguration().getClientSecret() + - "&redirect_uri=" + openIdConfiguration().getCallbackURI(); + "&client_id=" + openIdConfiguration().clientId() + + "&client_secret=" + openIdConfiguration().clientSecret() + + "&redirect_uri=" + openIdConfiguration().callbackURI(); } private Map<String, Object> retrieveAccessToken(String code){ try { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(buildClaimsRetrieverUrl())) - .header("Content-Type", openIdConfiguration().getContentType()) + .header("Content-Type", openIdConfiguration().contentType()) .POST(HttpRequest.BodyPublishers.ofString(buildRetrieveClaimsUrlBody(code))) .build(); diff --git a/src/main/java/alfio/manager/openid/OpenIdConfiguration.java b/src/main/java/alfio/manager/openid/OpenIdConfiguration.java index 33c1a3658f..6c3b17e166 100644 --- a/src/main/java/alfio/manager/openid/OpenIdConfiguration.java +++ b/src/main/java/alfio/manager/openid/OpenIdConfiguration.java @@ -21,31 +21,26 @@ import alfio.model.system.ConfigurationKeys; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.Setter; + import org.springframework.core.env.Environment; import static java.util.Objects.requireNonNullElse; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.removeEnd; -@Getter -@Setter -public class OpenIdConfiguration { - private final String domain; - private final String clientId; - private final String clientSecret; - private final String callbackURI; - private final String authenticationUrl; - private final String tokenEndpoint; - private final String givenNameClaim; - private final String familyNameClaim; - private final String contentType; - private final String rolesParameter; - private final String alfioGroupsParameter; - private final String logoutUrl; - private final String logoutRedirectUrl; - +public record OpenIdConfiguration(String domain, + String clientId, + String clientSecret, + String callbackURI, + String authenticationUrl, + String tokenEndpoint, + String givenNameClaim, + String familyNameClaim, + String contentType, + String rolesParameter, + String alfioGroupsParameter, + String logoutUrl, + String logoutRedirectUrl) { @JsonCreator public OpenIdConfiguration(@JsonProperty("domain") String domain, @JsonProperty("clientId") String clientId, @@ -81,7 +76,7 @@ public static OpenIdConfiguration from(Environment environment, ConfigurationMan environment.getProperty("openid.domain"), environment.getProperty("openid.clientId"), environment.getProperty("openid.clientSecret"), - environment.getProperty("openid.callbackURI",baseUrl + "/callback"), + environment.getProperty("openid.callbackURI", baseUrl + "/callback"), environment.getProperty("openid.authenticationUrl"), environment.getProperty("openid.tokenEndpoint", "/authorize"), environment.getProperty("openid.givenNameClaim"), @@ -95,16 +90,16 @@ public static OpenIdConfiguration from(Environment environment, ConfigurationMan } public String toString() { - return "OpenIdConfiguration(domain=" + this.getDomain() - + ", clientId=" + (isNotBlank(this.getClientId()) ? "<redacted>" : "missing") - + ", clientSecret=" + (isNotBlank(this.getClientSecret()) ? "<redacted>" : "missing") - + ", callbackURI=" + this.getCallbackURI() - + ", authenticationUrl=" + this.getAuthenticationUrl() - + ", tokenEndpoint=" + this.getTokenEndpoint() - + ", contentType=" + this.getContentType() - + ", rolesParameter=" + this.getRolesParameter() - + ", alfioGroupsParameter=" + this.getAlfioGroupsParameter() - + ", logoutUrl=" + this.getLogoutUrl() - + ", logoutRedirectUrl=" + this.getLogoutRedirectUrl() + ")"; + return "OpenIdConfiguration(domain=" + this.domain() + + ", clientId=" + (isNotBlank(this.clientId()) ? "<redacted>" : "missing") + + ", clientSecret=" + (isNotBlank(this.clientSecret()) ? "<redacted>" : "missing") + + ", callbackURI=" + this.callbackURI() + + ", authenticationUrl=" + this.authenticationUrl() + + ", tokenEndpoint=" + this.tokenEndpoint() + + ", contentType=" + this.contentType() + + ", rolesParameter=" + this.rolesParameter() + + ", alfioGroupsParameter=" + this.alfioGroupsParameter() + + ", logoutUrl=" + this.logoutUrl() + + ", logoutRedirectUrl=" + this.logoutRedirectUrl() + ")"; } } diff --git a/src/main/java/alfio/manager/payment/BankTransferManager.java b/src/main/java/alfio/manager/payment/BankTransferManager.java index 549a5e4a3d..02d942fdf8 100644 --- a/src/main/java/alfio/manager/payment/BankTransferManager.java +++ b/src/main/java/alfio/manager/payment/BankTransferManager.java @@ -18,6 +18,7 @@ import alfio.manager.support.PaymentResult; import alfio.manager.system.ConfigurationManager; +import alfio.model.Event; import alfio.model.PurchaseContext; import alfio.model.TicketReservation; import alfio.model.system.ConfigurationKeys; @@ -26,9 +27,9 @@ import alfio.repository.TransactionRepository; import alfio.util.ClockProvider; import alfio.util.WorkingDaysAdjusters; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.time.ZonedDateTime; @@ -40,18 +41,29 @@ import static alfio.model.system.ConfigurationKeys.*; @Component -@Log4j2 -@AllArgsConstructor public class BankTransferManager implements PaymentProvider { + private static final Logger log = LoggerFactory.getLogger(BankTransferManager.class); + private static final EnumSet<ConfigurationKeys> OPTIONS_TO_LOAD = EnumSet.of(BANK_TRANSFER_ENABLED, DEFERRED_BANK_TRANSFER_ENABLED, OFFLINE_PAYMENT_DAYS, REVOLUT_ENABLED, REVOLUT_API_KEY, REVOLUT_LIVE_MODE, REVOLUT_MANUAL_REVIEW); + private final ConfigurationManager configurationManager; private final TicketReservationRepository ticketReservationRepository; private final TransactionRepository transactionRepository; private final ClockProvider clockProvider; + public BankTransferManager(ConfigurationManager configurationManager, + TicketReservationRepository ticketReservationRepository, + TransactionRepository transactionRepository, + ClockProvider clockProvider) { + this.configurationManager = configurationManager; + this.ticketReservationRepository = ticketReservationRepository; + this.transactionRepository = transactionRepository; + this.clockProvider = clockProvider; + } + @Override public Set<PaymentMethod> getSupportedPaymentMethods(PaymentContext paymentContext, TransactionRequest transactionRequest) { return EnumSet.of(PaymentMethod.BANK_TRANSFER); @@ -151,14 +163,24 @@ public static OptionalInt getOfflinePaymentWaitingPeriod(PaymentContext paymentC private static OptionalInt getOfflinePaymentWaitingPeriod(PurchaseContext purchaseContext, int configuredValue) { ZonedDateTime now = purchaseContext.now(ClockProvider.clock()); - ZonedDateTime eventBegin = purchaseContext.getBegin(); - int daysToBegin = (int) ChronoUnit.DAYS.between(now.toLocalDate(), eventBegin.toLocalDate()); + ZonedDateTime maxDate = purchaseContext.event() + .map(BankTransferManager::getMaxPaymentDate) + .orElse(purchaseContext.getBegin()); + int daysToBegin = (int) ChronoUnit.DAYS.between(now.toLocalDate(), maxDate.toLocalDate()); if (daysToBegin < 0) { return OptionalInt.empty(); } return OptionalInt.of( Math.min(daysToBegin, configuredValue) ); } + private static ZonedDateTime getMaxPaymentDate(Event event) { + if (event.getSameDay()) { + return event.getBegin(); + } else { + return event.getEnd(); + } + } + @Override public boolean accept(Transaction transaction) { return PaymentProxy.OFFLINE == transaction.getPaymentProxy(); diff --git a/src/main/java/alfio/manager/payment/BaseStripeManager.java b/src/main/java/alfio/manager/payment/BaseStripeManager.java index 004bc7a2ff..e3e37088e8 100644 --- a/src/main/java/alfio/manager/payment/BaseStripeManager.java +++ b/src/main/java/alfio/manager/payment/BaseStripeManager.java @@ -41,8 +41,8 @@ import com.stripe.model.Refund; import com.stripe.net.RequestOptions; import com.stripe.net.Webhook; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; @@ -52,10 +52,10 @@ import static alfio.model.system.ConfigurationKeys.*; -@Log4j2 -@AllArgsConstructor class BaseStripeManager { + private static final Logger log = LoggerFactory.getLogger(BaseStripeManager.class); + static final String STRIPE_MANAGER_TYPE_KEY = "stripeManagerType"; static final String SUCCEEDED = "succeeded"; static final String PENDING = "pending"; @@ -76,6 +76,16 @@ class BaseStripeManager { Stripe.setAppInfo("Alf.io", "2.x", "https://alf.io"); } + BaseStripeManager(ConfigurationManager configurationManager, + ConfigurationRepository configurationRepository, + TicketRepository ticketRepository, + Environment environment) { + this.configurationManager = configurationManager; + this.configurationRepository = configurationRepository; + this.ticketRepository = ticketRepository; + this.environment = environment; + } + String getSecretKey(Configurable configurable) { return configurationManager.getFor(STRIPE_SECRET_KEY, configurable.getConfigurationLevel()).getRequiredValue(); } @@ -110,8 +120,7 @@ Optional<Boolean> processWebhookEvent(String body, String signature) { try { com.stripe.model.Event event = Webhook.constructEvent(body, signature, getWebhookSignatureKey()); if("account.application.deauthorized".equals(event.getType()) - && event.getLivemode() != null - && event.getLivemode() == environment.acceptsProfiles(Profiles.of("dev", "test", "demo"))) { + && Boolean.TRUE.equals(event.getLivemode()) == environment.acceptsProfiles(Profiles.of("dev", "test", "demo"))) { return Optional.of(revokeToken(event.getAccount())); } return Optional.of(true); @@ -194,10 +203,14 @@ PaymentResult getToken(PaymentSpecification spec) { return PaymentResult.failed(ErrorsCode.STEP_2_MISSING_STRIPE_TOKEN); } - private BalanceTransaction retrieveBalanceTransaction(String balanceTransaction, RequestOptions options) throws StripeException { + BalanceTransaction retrieveBalanceTransaction(String balanceTransaction, RequestOptions options) throws StripeException { return BalanceTransaction.retrieve(balanceTransaction, options); } + Charge retrieveCharge(String chargeId, RequestOptions requestOptions) throws StripeException { + return Charge.retrieve(chargeId, requestOptions); + } + Optional<RequestOptions> options(PurchaseContext purchaseContext) { return options(purchaseContext, UnaryOperator.identity()); } @@ -227,10 +240,10 @@ Optional<PaymentInformation> getInfo(Transaction transaction, PurchaseContext pu Optional<RequestOptions> requestOptionsOptional = options(purchaseContext); if(requestOptionsOptional.isPresent()) { RequestOptions options = requestOptionsOptional.get(); - Charge charge = Charge.retrieve(transaction.getTransactionId(), options); + Charge charge = retrieveCharge(transaction.getTransactionId(), options); String paidAmount = MonetaryUtil.formatCents(charge.getAmount(), charge.getCurrency()); String refundedAmount = MonetaryUtil.formatCents(charge.getAmountRefunded(), charge.getCurrency()); - List<BalanceTransaction.Fee> fees = retrieveBalanceTransaction(charge.getBalanceTransaction(), options).getFeeDetails(); + List<BalanceTransaction.FeeDetail> fees = retrieveBalanceTransaction(charge.getBalanceTransaction(), options).getFeeDetails(); return Optional.of(new PaymentInformation(paidAmount, refundedAmount, getFeeAmount(fees, "stripe_fee"), getFeeAmount(fees, "application_fee"))); } return Optional.empty(); @@ -239,11 +252,11 @@ Optional<PaymentInformation> getInfo(Transaction transaction, PurchaseContext pu } } - static String getFeeAmount(List<BalanceTransaction.Fee> fees, String feeType) { + static String getFeeAmount(List<BalanceTransaction.FeeDetail> fees, String feeType) { return fees.stream() .filter(f -> f.getType().equals(feeType)) .findFirst() - .map(BalanceTransaction.Fee::getAmount) + .map(BalanceTransaction.FeeDetail::getAmount) .map(String::valueOf) .orElse(null); } diff --git a/src/main/java/alfio/manager/payment/DeferredBankTransferManager.java b/src/main/java/alfio/manager/payment/DeferredBankTransferManager.java index 9d6fdcd80c..f111aab129 100644 --- a/src/main/java/alfio/manager/payment/DeferredBankTransferManager.java +++ b/src/main/java/alfio/manager/payment/DeferredBankTransferManager.java @@ -19,7 +19,6 @@ import alfio.manager.support.PaymentResult; import alfio.model.transaction.*; import alfio.repository.TicketReservationRepository; -import lombok.extern.log4j.Log4j2; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @@ -30,7 +29,6 @@ @Component @Order(0) -@Log4j2 public class DeferredBankTransferManager implements PaymentProvider { private final TicketReservationRepository ticketReservationRepository; diff --git a/src/main/java/alfio/manager/payment/MetadataBuilder.java b/src/main/java/alfio/manager/payment/MetadataBuilder.java index 22ba7e5a49..d72dd5555d 100644 --- a/src/main/java/alfio/manager/payment/MetadataBuilder.java +++ b/src/main/java/alfio/manager/payment/MetadataBuilder.java @@ -16,19 +16,19 @@ */ package alfio.manager.payment; -import lombok.experimental.UtilityClass; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -@UtilityClass -class MetadataBuilder { - +final class MetadataBuilder { static final String RESERVATION_ID = "reservationId"; + private MetadataBuilder() { + } + static Map<String, String> buildMetadata(PaymentSpecification spec, Map<String, String> base) { Map<String, String> initialMetadata = new HashMap<>(base); initialMetadata.put(RESERVATION_ID, spec.getReservationId()); diff --git a/src/main/java/alfio/manager/payment/MollieConnectManager.java b/src/main/java/alfio/manager/payment/MollieConnectManager.java index 9c4f314203..47e3a6f79f 100644 --- a/src/main/java/alfio/manager/payment/MollieConnectManager.java +++ b/src/main/java/alfio/manager/payment/MollieConnectManager.java @@ -27,10 +27,9 @@ import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.builder.api.DefaultApi20; import com.github.scribejava.core.model.OAuth2AccessToken; -import com.github.scribejava.core.model.OAuthConfig; import com.github.scribejava.core.oauth.OAuth20Service; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.net.http.HttpClient; @@ -41,23 +40,32 @@ import static alfio.model.system.ConfigurationKeys.*; -@AllArgsConstructor -@Log4j2 @Component public class MollieConnectManager implements OAuthPaymentProviderConnector { + + private static final Logger log = LoggerFactory.getLogger(MollieConnectManager.class); + public static final String MOLLIE_CONNECT_REDIRECT_PATH = "/admin/configuration/payment/mollie/authorize"; private static final String SCOPES = "payments.read payments.write refunds.read refunds.write"; private final ExtensionManager extensionManager; private final ConfigurationManager configurationManager; private final HttpClient httpClient; + public MollieConnectManager(ExtensionManager extensionManager, + ConfigurationManager configurationManager, + HttpClient httpClient) { + this.extensionManager = extensionManager; + this.configurationManager = configurationManager; + this.httpClient = httpClient; + } + @Override public AuthorizationRequestDetails getConnectURL(int organizationId) { var options = configurationManager.getFor(Set.of(MOLLIE_API_KEY, MOLLIE_CONNECT_CLIENT_ID, MOLLIE_CONNECT_CALLBACK, BASE_URL), ConfigurationLevel.organization(organizationId)); String callbackURL = options.get(MOLLIE_CONNECT_CALLBACK).getValueOrDefault(options.get(BASE_URL).getRequiredValue() + MOLLIE_CONNECT_REDIRECT_PATH); String state = extensionManager.generateOAuth2StateParam(organizationId).orElse(UUID.randomUUID().toString()); - OAuthConfig config = new OAuthConfig(options.get(MOLLIE_CONNECT_CLIENT_ID).getRequiredValue(), options.get(MOLLIE_API_KEY).getRequiredValue(), callbackURL, SCOPES, null, state, "code", null, null, null); - return new AuthorizationRequestDetails(new MollieConnectApi().getAuthorizationUrl(config, Collections.emptyMap()), state); + return new AuthorizationRequestDetails(new MollieConnectApi() + .getAuthorizationUrl("code", options.get(MOLLIE_CONNECT_CLIENT_ID).getRequiredValue(), callbackURL, SCOPES, state, Collections.emptyMap()), state); } @Override diff --git a/src/main/java/alfio/manager/payment/MollieWebhookPaymentManager.java b/src/main/java/alfio/manager/payment/MollieWebhookPaymentManager.java index 0de1a58e37..a8448f613e 100644 --- a/src/main/java/alfio/manager/payment/MollieWebhookPaymentManager.java +++ b/src/main/java/alfio/manager/payment/MollieWebhookPaymentManager.java @@ -37,16 +37,16 @@ import alfio.repository.TicketReservationRepository; import alfio.repository.TransactionRepository; import alfio.util.*; -import alfio.util.oauth2.AccessTokenResponseDetails; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import lombok.AllArgsConstructor; import lombok.Data; -import lombok.extern.log4j.Log4j2; +import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; @@ -70,10 +70,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; @Component -@Log4j2 -@AllArgsConstructor public class MollieWebhookPaymentManager implements PaymentProvider, WebhookHandler, RefundRequest, PaymentInfo { + private static final Logger log = LoggerFactory.getLogger(MollieWebhookPaymentManager.class); + public static final String WEBHOOK_URL_TEMPLATE = "/api/payment/webhook/mollie/reservation/{reservationId}"; private static final Set<PaymentMethod> EMPTY_METHODS = Collections.unmodifiableSet(EnumSet.noneOf(PaymentMethod.class)); static final Map<String, PaymentMethod> SUPPORTED_METHODS = Map.of( @@ -123,6 +123,24 @@ public class MollieWebhookPaymentManager implements PaymentProvider, WebhookHand private final ClockProvider clockProvider; private final PurchaseContextManager purchaseContextManager; + public MollieWebhookPaymentManager(HttpClient client, + ConfigurationManager configurationManager, + TicketReservationRepository ticketReservationRepository, + TicketRepository ticketRepository, + TransactionRepository transactionRepository, + MollieConnectManager mollieConnectManager, + ClockProvider clockProvider, + PurchaseContextManager purchaseContextManager) { + this.client = client; + this.configurationManager = configurationManager; + this.ticketReservationRepository = ticketReservationRepository; + this.ticketRepository = ticketRepository; + this.transactionRepository = transactionRepository; + this.mollieConnectManager = mollieConnectManager; + this.clockProvider = clockProvider; + this.purchaseContextManager = purchaseContextManager; + } + private HttpRequest.Builder requestFor(String url, Map<ConfigurationKeys, MaybeConfiguration> configuration, ConfigurationLevel configurationLevel) { // check if platform mode is active boolean platformMode = configurationLevel.getOrganizationId().isPresent() && configuration.get(PLATFORM_MODE_ENABLED).getValueAsBooleanOrDefault(); @@ -130,8 +148,8 @@ private HttpRequest.Builder requestFor(String url, Map<ConfigurationKeys, MaybeC if(platformMode) { return requestBuilder.header("Authorization", "Bearer " + accessTokenCache.get(configurationLevel.getOrganizationId().orElseThrow(), org -> { var accessTokenResponseDetails = mollieConnectManager.refreshAccessToken(configuration); - if(accessTokenResponseDetails.isSuccess()) { - return accessTokenResponseDetails.getAccessToken(); + if(accessTokenResponseDetails.success()) { + return accessTokenResponseDetails.accessToken(); } throw new IllegalStateException("cannot refresh access token"); })); @@ -274,7 +292,7 @@ private PaymentResult getPaymentResult(PaymentSpecification spec) { log.warn("Request interrupted while trying to init payment", e); return PaymentResult.failed(ErrorsCode.STEP_2_PAYMENT_REQUEST_CREATION); } catch (Exception e) { - log.warn(e); + log.warn("Error while getting the payment result", e); return PaymentResult.failed(ErrorsCode.STEP_2_PAYMENT_REQUEST_CREATION); } } @@ -547,12 +565,12 @@ public boolean refund(Transaction transaction, PurchaseContext purchaseContext, try { var response = client.send(request, HttpResponse.BodyHandlers.ofString()); if(HttpUtils.callSuccessful(response)) { - log.trace("Received a successful response from Mollie. Body is {}", response::body); + log.trace("Received a successful response from Mollie. Body is {}", response.body()); // we ignore the answer, for now return true; } else { log.warn("got {} response while calling refund API for payment ID {}", response.statusCode(), paymentId); - log.trace("detailed reply from mollie: {}", response::body); + log.trace("detailed reply from mollie: {}", response.body()); return false; } } catch (InterruptedException e) { @@ -598,29 +616,37 @@ private static MethodCacheKey from(TransactionRequest transactionRequest, Map<ConfigurationKeys, MaybeConfiguration> configuration) { String billingCountry = null; - if(transactionRequest.getBillingDetails() != null) { - billingCountry = StringUtils.trimToNull(transactionRequest.getBillingDetails().getCountry()); + if(transactionRequest.billingDetails() != null) { + billingCountry = StringUtils.trimToNull(transactionRequest.billingDetails().getCountry()); } PaymentAmount amount = null; - if(transactionRequest.getPrice() != null) { - String currencyCode = transactionRequest.getPrice().getCurrencyCode(); - amount = new PaymentAmount(formatCents(transactionRequest.getPrice().getPriceWithVAT(), currencyCode), currencyCode); + if(transactionRequest.price() != null) { + String currencyCode = transactionRequest.price().currencyCode(); + amount = new PaymentAmount(formatCents(transactionRequest.price().priceWithVAT(), currencyCode), currencyCode); } boolean testMode = !configuration.get(MOLLIE_CONNECT_LIVE_MODE).getValueAsBooleanOrDefault(); return new MethodCacheKey(amount, billingCountry, testMode); } } - @Data + @Getter public static class PaymentAmount { private final String value; private final String currency; + + public PaymentAmount(String value, String currency) { + this.value = value; + this.currency = currency; + } } - @AllArgsConstructor private static class MolliePaymentDetails { private final JsonObject body; + private MolliePaymentDetails(JsonObject body) { + this.body = body; + } + String getReservationId() { return Objects.requireNonNull(body.getAsJsonObject("metadata").get(MetadataBuilder.RESERVATION_ID), "reservation id") .getAsString(); diff --git a/src/main/java/alfio/manager/payment/OnSiteManager.java b/src/main/java/alfio/manager/payment/OnSiteManager.java index 88307bfcc9..2e25a416c7 100644 --- a/src/main/java/alfio/manager/payment/OnSiteManager.java +++ b/src/main/java/alfio/manager/payment/OnSiteManager.java @@ -21,8 +21,6 @@ import alfio.model.system.ConfigurationPathLevel; import alfio.model.transaction.*; import alfio.repository.TransactionRepository; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -36,14 +34,17 @@ import static alfio.model.system.ConfigurationKeys.RECAPTCHA_API_KEY; @Component -@Log4j2 -@AllArgsConstructor @Transactional public class OnSiteManager implements PaymentProvider { private final ConfigurationManager configurationManager; private final TransactionRepository transactionRepository; + public OnSiteManager(ConfigurationManager configurationManager, TransactionRepository transactionRepository) { + this.configurationManager = configurationManager; + this.transactionRepository = transactionRepository; + } + @Override public Set<PaymentMethod> getSupportedPaymentMethods(PaymentContext paymentContext, TransactionRequest transactionRequest) { return EnumSet.of(PaymentMethod.ON_SITE); diff --git a/src/main/java/alfio/manager/payment/PayPalManager.java b/src/main/java/alfio/manager/payment/PayPalManager.java index d34eeaa580..b0f013e820 100644 --- a/src/main/java/alfio/manager/payment/PayPalManager.java +++ b/src/main/java/alfio/manager/payment/PayPalManager.java @@ -40,13 +40,13 @@ import com.paypal.http.exceptions.HttpException; import com.paypal.orders.*; import com.paypal.payments.CapturesRefundRequest; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.codec.digest.HmacUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; @@ -58,17 +58,16 @@ import java.time.ZonedDateTime; import java.util.*; import java.util.function.Supplier; -import java.util.stream.Collectors; import static alfio.model.system.ConfigurationKeys.PAYPAL_ENABLED; import static alfio.util.MonetaryUtil.*; import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; @Component -@Log4j2 -@AllArgsConstructor public class PayPalManager implements PaymentProvider, RefundRequest, PaymentInfo, ExtractPaymentTokenFromTransaction { + private static final Logger log = LoggerFactory.getLogger(PayPalManager.class); + private final Cache<String, PayPalHttpClient> cachedClients = Caffeine.newBuilder() .expireAfterAccess(Duration.ofHours(1L)) .build(); @@ -79,6 +78,20 @@ public class PayPalManager implements PaymentProvider, RefundRequest, PaymentInf private final Json json; private final ClockProvider clockProvider; + public PayPalManager(ConfigurationManager configurationManager, + TicketReservationRepository ticketReservationRepository, + TicketRepository ticketRepository, + TransactionRepository transactionRepository, + Json json, + ClockProvider clockProvider) { + this.configurationManager = configurationManager; + this.ticketReservationRepository = ticketReservationRepository; + this.ticketRepository = ticketRepository; + this.transactionRepository = transactionRepository; + this.json = json; + this.clockProvider = clockProvider; + } + private PayPalHttpClient getClient(Configurable configurable) { PayPalEnvironment apiContext = getApiContext(configurable); return cachedClients.get(generateKey(apiContext), key -> new PayPalHttpClient(apiContext)); @@ -232,8 +245,7 @@ private Optional<PaymentInformation> getInfo(Transaction transaction, PurchaseCo if(HttpUtils.statusCodeIsSuccessful(orderResponse.statusCode()) && orderResponse.result() != null) { var order = orderResponse.result(); var payments = order.purchaseUnits().stream() - .map(PurchaseUnit::payments) - .collect(Collectors.toList()); + .map(PurchaseUnit::payments).toList(); var refund = payments.stream().flatMap(p -> emptyIfNull(p.refunds()).stream()) .map(r -> new BigDecimal(r.amount().value())) @@ -349,7 +361,7 @@ public PaymentResult getToken(PaymentSpecification spec) { } return PaymentResult.redirect(createCheckoutRequest(spec)); } catch (Exception e) { - log.error(e); + log.error("error while getting the token", e); return PaymentResult.failed( ErrorsCode.STEP_2_PAYMENT_REQUEST_CREATION ); } } @@ -413,11 +425,8 @@ public void removeToken(TicketReservation reservation, String paymentId) { } } - @AllArgsConstructor - private static class PayPalChargeDetails { - private final String captureId; - private final String orderId; - private final long payPalFee; + + private record PayPalChargeDetails(String captureId, String orderId, long payPalFee) { } } diff --git a/src/main/java/alfio/manager/payment/PaymentManagerUtils.java b/src/main/java/alfio/manager/payment/PaymentManagerUtils.java index 36a1dea128..ec1560b5c5 100644 --- a/src/main/java/alfio/manager/payment/PaymentManagerUtils.java +++ b/src/main/java/alfio/manager/payment/PaymentManagerUtils.java @@ -18,13 +18,16 @@ import alfio.model.transaction.PaymentProxy; import alfio.repository.TransactionRepository; -import lombok.experimental.UtilityClass; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -@UtilityClass -@Log4j2 -class PaymentManagerUtils { +final class PaymentManagerUtils { + + private static final Logger log = LoggerFactory.getLogger(PaymentManagerUtils.class); + + private PaymentManagerUtils() { + } static void invalidateExistingTransactions(String reservationId, TransactionRepository transactionRepository) { invalidateExistingTransactions(reservationId, transactionRepository, null); diff --git a/src/main/java/alfio/manager/payment/PaymentSpecification.java b/src/main/java/alfio/manager/payment/PaymentSpecification.java index cec14d826f..c6187ca8d7 100644 --- a/src/main/java/alfio/manager/payment/PaymentSpecification.java +++ b/src/main/java/alfio/manager/payment/PaymentSpecification.java @@ -87,7 +87,7 @@ public PaymentSpecification(TicketReservation reservation, OrderSummary orderSummary, boolean tcAccepted, boolean privacyAccepted) { - this(reservation.getId(), gatewayToken, totalPrice.getPriceWithVAT(), + this(reservation.getId(), gatewayToken, totalPrice.priceWithVAT(), purchaseContext, reservation.getEmail(), new CustomerName(reservation.getFullName(), reservation.getFirstName(), reservation.getLastName(), true), reservation.getBillingAddress(), reservation.getCustomerReference(), LocaleUtil.forLanguageTag(reservation.getUserLanguage()), reservation.isInvoiceRequested(), !reservation.isDirectAssignmentRequested(), orderSummary, reservation.getVatCountryCode(), diff --git a/src/main/java/alfio/manager/payment/RevolutBankTransferManager.java b/src/main/java/alfio/manager/payment/RevolutBankTransferManager.java index c8b37ee1fc..e84a1bd6c2 100644 --- a/src/main/java/alfio/manager/payment/RevolutBankTransferManager.java +++ b/src/main/java/alfio/manager/payment/RevolutBankTransferManager.java @@ -31,13 +31,12 @@ import alfio.util.ClockProvider; import alfio.util.Json; import alfio.util.MonetaryUtil; -import alfio.util.oauth2.AccessTokenResponseDetails; import com.fasterxml.jackson.core.type.TypeReference; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -52,7 +51,6 @@ import java.time.ZonedDateTime; import java.util.*; import java.util.function.Predicate; -import java.util.stream.Collectors; import static alfio.model.system.ConfigurationKeys.*; import static alfio.util.EventUtil.JSON_DATETIME_FORMATTER; @@ -60,10 +58,10 @@ @Component @Order(1) @Transactional -@Log4j2 -@AllArgsConstructor public class RevolutBankTransferManager implements PaymentProvider, OfflineProcessor, PaymentInfo { + private static final Logger log = LoggerFactory.getLogger(RevolutBankTransferManager.class); + private static final String GENERIC_ERROR = "error"; private final BankTransferManager bankTransferManager; private final ConfigurationManager configurationManager; @@ -74,6 +72,18 @@ public class RevolutBankTransferManager implements PaymentProvider, OfflineProce .expireAfterWrite(Duration.ofHours(1)) .build(); + public RevolutBankTransferManager(BankTransferManager bankTransferManager, + ConfigurationManager configurationManager, + TransactionRepository transactionRepository, + HttpClient client, + ClockProvider clockProvider) { + this.bankTransferManager = bankTransferManager; + this.configurationManager = configurationManager; + this.transactionRepository = transactionRepository; + this.client = client; + this.clockProvider = clockProvider; + } + @Override public Set<PaymentMethod> getSupportedPaymentMethods(PaymentContext paymentContext, TransactionRequest transactionRequest) { return bankTransferManager.getSupportedPaymentMethods(paymentContext, transactionRequest); @@ -134,8 +144,7 @@ Result<List<String>> matchTransactions(Collection<TicketReservationWithTransacti List<Pair<TicketReservationWithTransaction, RevolutTransactionDescriptor>> matched = pendingReservations.stream() .map(reservation -> Pair.of(reservation, transactions.stream().filter(transactionMatches(reservation, context)).findFirst())) .filter(pair -> pair.getRight().isPresent()) - .map(pair -> Pair.of(pair.getLeft(), pair.getRight().orElseThrow())) - .collect(Collectors.toList()); + .map(pair -> Pair.of(pair.getLeft(), pair.getRight().orElseThrow())).toList(); return Result.success(matched.stream().map(pair -> { var reservationId = pair.getLeft().getTicketReservation().getId(); @@ -158,7 +167,7 @@ Result<List<String>> matchTransactions(Collection<TicketReservationWithTransacti return reservationId; }) .filter(Objects::nonNull) - .collect(Collectors.toList())); + .toList()); } private Predicate<RevolutTransactionDescriptor> transactionMatches(TicketReservationWithTransaction reservationWithTransaction, PaymentContext context) { @@ -195,7 +204,7 @@ private Result<List<RevolutTransactionDescriptor>> loadTransactions(ZonedDateTim return Result.success( result.stream() .filter(t -> "completed".equals(t.getState()) && t.getLegs().size() == 1 && accounts.contains(t.getLegs().get(0).getAccountId())) - .collect(Collectors.toList())); + .toList()); } return Result.error(ErrorCode.custom("no data received", "No data received from Revolut. Status code is "+response.statusCode())); } catch (InterruptedException e) { @@ -227,7 +236,7 @@ private List<String> loadAccountsFromAPI(String revolutKey, String baseUrl) { if (response.statusCode() == HttpStatus.OK.value()) { List<Map<String, ?>> result = Json.fromJson(response.body(), new TypeReference<>() { }); - return result.stream().filter(m -> "active".equals(m.get("state"))).map(m -> (String) m.get("id")).collect(Collectors.toList()); + return result.stream().filter(m -> "active".equals(m.get("state"))).map(m -> (String) m.get("id")).toList(); } throw new IllegalStateException("cannot retrieve accounts"); } catch (IllegalStateException ex) { diff --git a/src/main/java/alfio/manager/payment/SaferpayManager.java b/src/main/java/alfio/manager/payment/SaferpayManager.java index 2a608b7b77..46115fe100 100644 --- a/src/main/java/alfio/manager/payment/SaferpayManager.java +++ b/src/main/java/alfio/manager/payment/SaferpayManager.java @@ -35,9 +35,7 @@ import alfio.util.ClockProvider; import alfio.util.HttpUtils; import alfio.util.MonetaryUtil; -import alfio.util.oauth2.AccessTokenResponseDetails; import com.google.gson.JsonParser; -import lombok.AllArgsConstructor; import lombok.experimental.Delegate; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; @@ -61,7 +59,6 @@ @Component @Transactional -@AllArgsConstructor public class SaferpayManager implements PaymentProvider, /*RefundRequest,*/ PaymentInfo, WebhookHandler { private static final String LIVE_ENDPOINT = "https://www.saferpay.com/api"; @@ -78,6 +75,20 @@ public class SaferpayManager implements PaymentProvider, /*RefundRequest,*/ Paym private final TicketRepository ticketRepository; private final ClockProvider clockProvider; + public SaferpayManager(ConfigurationManager configurationManager, + HttpClient httpClient, + TicketReservationRepository ticketReservationRepository, + TransactionRepository transactionRepository, + TicketRepository ticketRepository, + ClockProvider clockProvider) { + this.configurationManager = configurationManager; + this.httpClient = httpClient; + this.ticketReservationRepository = ticketReservationRepository; + this.transactionRepository = transactionRepository; + this.ticketRepository = ticketRepository; + this.clockProvider = clockProvider; + } + @Override public Set<PaymentMethod> getSupportedPaymentMethods(PaymentContext paymentContext, TransactionRequest transactionRequest) { return EnumSet.of(PaymentMethod.CREDIT_CARD); @@ -139,13 +150,14 @@ public PaymentResult doPayment(PaymentSpecification spec) { var description = purchaseContext.ofType(PurchaseContext.PurchaseContextType.event) ? "ticket(s) for event" : "x subscription"; var paymentDescription = String.format("%s - %d %s %s", configurationManager.getShortReservationID(purchaseContext, reservation), items, description, purchaseContext.getDisplayName()); - var requestBody = new PaymentPageInitializeRequestBuilder(configuration.get(BASE_URL).getRequiredValue(), spec) - .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), reservationId, configuration.get(SAFERPAY_TERMINAL_ID).getRequiredValue()) - .addOrderInformation(reservationId, Integer.toString(spec.getPriceWithVAT()), spec.getCurrencyCode(), paymentDescription, retryCount) - .build(); - var request = buildRequest(configuration, "/Payment/v1/PaymentPage/Initialize", requestBody); + try { + var requestBody = new PaymentPageInitializeRequestBuilder(configuration.get(BASE_URL).getRequiredValue(), spec) + .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), reservationId, configuration.get(SAFERPAY_TERMINAL_ID).getRequiredValue()) + .addOrderInformation(reservationId, Integer.toString(spec.getPriceWithVAT()), spec.getCurrencyCode(), paymentDescription, retryCount) + .build(); + var request = buildRequest(configuration, "/Payment/v1/PaymentPage/Initialize", requestBody); var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if(!HttpUtils.callSuccessful(response)) { LOGGER.warn("Create session failed with status {}, body {}", response.statusCode(), response.body()); @@ -217,12 +229,11 @@ public boolean requiresSignedBody() { @Override public Optional<PaymentInformation> getInfo(Transaction transaction, PurchaseContext purchaseContext) { var configuration = loadConfiguration(purchaseContext); - var requestBody = new TransactionInquireRequestBuilder(transaction.getTransactionId(), 0) - .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), transaction.getReservationId()) - .build(); - var request = buildRequest(configuration, "/Payment/v1/Transaction/Inquire", requestBody); - try { + var requestBody = new TransactionInquireRequestBuilder(transaction.getTransactionId(), 0) + .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), transaction.getReservationId()) + .build(); + var request = buildRequest(configuration, "/Payment/v1/Transaction/Inquire", requestBody); var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if(!HttpUtils.callSuccessful(response)) { LOGGER.warn("Cannot retrieve transaction info. Status {}, body {}", response.statusCode(), response.body()); @@ -246,11 +257,11 @@ public Optional<PaymentInformation> getInfo(Transaction transaction, PurchaseCon //@Override public boolean refund(Transaction transaction, PurchaseContext purchaseContext, Integer amount) { var configuration = loadConfiguration(purchaseContext); - var requestBody = new TransactionRefundBuilder(transaction.getPaymentId(), 0) - .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), transaction.getReservationId()) - .build(Integer.toString(amount), transaction.getCurrency()); - var request = buildRequest(configuration, "/Payment/v1/Transaction/Refund", requestBody); try { + var requestBody = new TransactionRefundBuilder(transaction.getPaymentId(), 0) + .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), transaction.getReservationId()) + .build(Integer.toString(amount), transaction.getCurrency()); + var request = buildRequest(configuration, "/Payment/v1/Transaction/Refund", requestBody); var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if(!HttpUtils.callSuccessful(response)) { LOGGER.warn("Cannot refund transaction. Status {}, body {}", response.statusCode(), response.body()); @@ -277,11 +288,11 @@ private PaymentStatus retrievePaymentStatus(Map<ConfigurationKeys, Configuration String token, String reservationId, int retryCount) { - var requestBody = new PaymentPageAssertRequestBuilder(token, retryCount) - .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), reservationId) - .build(); - var request = buildRequest(configuration, "/Payment/v1/PaymentPage/Assert", requestBody); try { + var requestBody = new PaymentPageAssertRequestBuilder(token, retryCount) + .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), reservationId) + .build(); + var request = buildRequest(configuration, "/Payment/v1/PaymentPage/Assert", requestBody); var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (HttpUtils.callSuccessful(response)) { var responseBody = JsonParser.parseString(response.body()).getAsJsonObject(); @@ -322,12 +333,11 @@ private PaymentStatus confirmTransaction(Map<ConfigurationKeys, ConfigurationMan String token, String requestId, int retryCount) { - var requestBody = new TransactionCaptureRequestBuilder(transactionId, retryCount) - .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), requestId) - .build(); - var request = buildRequest(configuration, "/Payment/v1/Transaction/Capture", requestBody); - try { + var requestBody = new TransactionCaptureRequestBuilder(transactionId, retryCount) + .addAuthentication(configuration.get(SAFERPAY_CUSTOMER_ID).getRequiredValue(), requestId) + .build(); + var request = buildRequest(configuration, "/Payment/v1/Transaction/Capture", requestBody); var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (HttpUtils.callSuccessful(response)) { var responseBody = JsonParser.parseString(response.body()).getAsJsonObject(); @@ -383,7 +393,7 @@ private String processPaymentInitializationResponse(HttpResponse<String> respons return responseBody.get("RedirectUrl").getAsString(); } - @AllArgsConstructor + private static class PaymentStatus { static final PaymentStatus EMPTY = new PaymentStatus(null, null, null, null); @@ -393,6 +403,13 @@ private static class PaymentStatus { private final String captureId; private final ZonedDateTime timestamp; + private PaymentStatus(PaymentResult paymentResult, String transactionId, String captureId, ZonedDateTime timestamp) { + this.paymentResult = paymentResult; + this.transactionId = transactionId; + this.captureId = captureId; + this.timestamp = timestamp; + } + private boolean isEmpty() { return paymentResult == null; diff --git a/src/main/java/alfio/manager/payment/StripeConnectManager.java b/src/main/java/alfio/manager/payment/StripeConnectManager.java index e1431134cf..3d866605bc 100644 --- a/src/main/java/alfio/manager/payment/StripeConnectManager.java +++ b/src/main/java/alfio/manager/payment/StripeConnectManager.java @@ -29,9 +29,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.builder.api.DefaultApi20; -import com.github.scribejava.core.model.OAuthConfig; import com.github.scribejava.core.oauth.OAuth20Service; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @@ -43,9 +43,10 @@ import static alfio.model.system.ConfigurationKeys.*; @Component -@Log4j2 public class StripeConnectManager implements OAuthPaymentProviderConnector { + private static final Logger log = LoggerFactory.getLogger(StripeConnectManager.class); + public static final String STRIPE_CONNECT_REDIRECT_PATH = "/admin/configuration/payment/stripe/authorize"; private final ExtensionManager extensionManager; private final ConfigurationManager configurationManager; @@ -64,12 +65,11 @@ public StripeConnectManager(ExtensionManager extensionManager, @Override public AuthorizationRequestDetails getConnectURL(int organizationId) { var options = configurationManager.getFor(Set.of(STRIPE_SECRET_KEY, STRIPE_CONNECT_CLIENT_ID, STRIPE_CONNECT_CALLBACK, BASE_URL), ConfigurationLevel.organization(organizationId)); - String secret = options.get(STRIPE_SECRET_KEY).getRequiredValue(); String clientId = options.get(STRIPE_CONNECT_CLIENT_ID).getRequiredValue(); String callbackURL = options.get(STRIPE_CONNECT_CALLBACK).getValueOrDefault(options.get(BASE_URL).getRequiredValue() + STRIPE_CONNECT_REDIRECT_PATH); String state = extensionManager.generateOAuth2StateParam(organizationId).orElse(UUID.randomUUID().toString()); - OAuthConfig config = new OAuthConfig(clientId, secret, callbackURL, "read_write", null, state, "code", null, null, null); - return new AuthorizationRequestDetails(new StripeConnectApi().getAuthorizationUrl(config, Collections.emptyMap()), state); + return new AuthorizationRequestDetails(new StripeConnectApi() + .getAuthorizationUrl("code", clientId, callbackURL, "read_write", state, Collections.emptyMap()), state); } @Override diff --git a/src/main/java/alfio/manager/payment/StripeCreditCardManager.java b/src/main/java/alfio/manager/payment/StripeCreditCardManager.java index c58752354f..77682383c9 100644 --- a/src/main/java/alfio/manager/payment/StripeCreditCardManager.java +++ b/src/main/java/alfio/manager/payment/StripeCreditCardManager.java @@ -34,8 +34,9 @@ import com.stripe.exception.StripeException; import com.stripe.model.BalanceTransaction; import com.stripe.model.Charge; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @@ -47,12 +48,13 @@ import static alfio.model.system.ConfigurationKeys.*; @Component -@Log4j2 public class StripeCreditCardManager implements PaymentProvider, ClientServerTokenRequest, RefundRequest, PaymentInfo { + private static final Logger log = LoggerFactory.getLogger(StripeCreditCardManager.class); + public static final String STRIPE_UNEXPECTED = "error.STEP2_STRIPE_unexpected"; private static final String STRIPE_MANAGER = StripeCreditCardManager.class.getName(); - public static final EnumSet<ConfigurationKeys> OPTIONS_TO_LOAD = EnumSet.of(STRIPE_ENABLE_SCA, STRIPE_SECRET_KEY, STRIPE_PUBLIC_KEY); + protected static final EnumSet<ConfigurationKeys> OPTIONS_TO_LOAD = EnumSet.of(STRIPE_ENABLE_SCA, STRIPE_SECRET_KEY, STRIPE_PUBLIC_KEY); private final TransactionRepository transactionRepository; private final BaseStripeManager baseStripeManager; @@ -151,7 +153,7 @@ public PaymentResult doPayment( PaymentSpecification spec ) { return optionalCharge.map(charge -> { log.info("transaction {} paid: {}", spec.getReservationId(), charge.getPaid()); Pair<Long, Long> fees = Optional.ofNullable(charge.getBalanceTransactionObject()).map( bt -> { - List<BalanceTransaction.Fee> feeDetails = bt.getFeeDetails(); + List<BalanceTransaction.FeeDetail> feeDetails = bt.getFeeDetails(); return Pair.of(Optional.ofNullable( BaseStripeManager.getFeeAmount(feeDetails, "application_fee")).map(Long::parseLong).orElse(0L), Optional.ofNullable( BaseStripeManager.getFeeAmount(feeDetails, "stripe_fee")).map(Long::parseLong).orElse(0L)); }).orElse(null); @@ -162,10 +164,9 @@ public PaymentResult doPayment( PaymentSpecification spec ) { fees != null ? fees.getLeft() : 0L, fees != null ? fees.getRight() : 0L, Transaction.Status.COMPLETE, Map.of(STRIPE_MANAGER_TYPE_KEY, STRIPE_MANAGER)); return PaymentResult.successful(charge.getId()); }).orElseGet(() -> PaymentResult.failed("error.STEP2_UNABLE_TO_TRANSITION")); + } catch (StripeException e) { + return PaymentResult.failed(baseStripeManager.handleException(e)); } catch (Exception e) { - if(e instanceof StripeException) { - return PaymentResult.failed( baseStripeManager.handleException((StripeException)e)); - } throw new IllegalStateException(e); } } diff --git a/src/main/java/alfio/manager/payment/StripeWebhookPaymentManager.java b/src/main/java/alfio/manager/payment/StripeWebhookPaymentManager.java index d790457ef4..e6bc105efa 100644 --- a/src/main/java/alfio/manager/payment/StripeWebhookPaymentManager.java +++ b/src/main/java/alfio/manager/payment/StripeWebhookPaymentManager.java @@ -36,13 +36,17 @@ import com.google.gson.JsonParser; import com.stripe.Stripe; import com.stripe.exception.StripeException; -import com.stripe.model.BalanceTransaction; import com.stripe.model.Charge; import com.stripe.model.PaymentIntent; import com.stripe.model.StripeObject; +import com.stripe.net.HttpHeaders; +import com.stripe.net.RequestOptions; +import com.stripe.net.StripeResponse; import com.stripe.net.Webhook; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -55,12 +59,14 @@ import static alfio.model.TicketReservation.TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT; import static alfio.model.TicketReservation.TicketReservationStatus.WAITING_EXTERNAL_CONFIRMATION; import static alfio.model.system.ConfigurationKeys.*; +import static java.util.Objects.requireNonNull; -@Log4j2 @Component @Transactional public class StripeWebhookPaymentManager implements PaymentProvider, RefundRequest, PaymentInfo, WebhookHandler, ClientServerTokenRequest, ServerInitiatedTransaction { + private static final Logger log = LoggerFactory.getLogger(StripeWebhookPaymentManager.class); + private static final String STRIPE_MANAGER = StripeWebhookPaymentManager.class.getName(); static final String CLIENT_SECRET_METADATA = "clientSecret"; private static final String PAYMENT_INTENT_SUCCEEDED = "payment_intent.succeeded"; @@ -78,6 +84,7 @@ public class StripeWebhookPaymentManager implements PaymentProvider, RefundReque private final List<String> interestingEventTypes = List.of(PAYMENT_INTENT_SUCCEEDED, PAYMENT_INTENT_PAYMENT_FAILED, PAYMENT_INTENT_CREATED); private final Set<String> cancellableStatuses = Set.of(REQUIRES_PAYMENT_METHOD, "requires_confirmation", "requires_action"); + @Autowired public StripeWebhookPaymentManager(ConfigurationManager configurationManager, TicketRepository ticketRepository, TransactionRepository transactionRepository, @@ -87,12 +94,28 @@ public StripeWebhookPaymentManager(ConfigurationManager configurationManager, AuditingRepository auditingRepository, Environment environment, ClockProvider clockProvider) { + this(configurationManager, + transactionRepository, + ticketReservationRepository, + eventRepository, + auditingRepository, + clockProvider, + new BaseStripeManager(configurationManager, configurationRepository, ticketRepository, environment)); + } + + StripeWebhookPaymentManager(ConfigurationManager configurationManager, + TransactionRepository transactionRepository, + TicketReservationRepository ticketReservationRepository, + EventRepository eventRepository, + AuditingRepository auditingRepository, + ClockProvider clockProvider, + BaseStripeManager baseStripeManager) { this.configurationManager = configurationManager; this.transactionRepository = transactionRepository; this.ticketReservationRepository = ticketReservationRepository; this.eventRepository = eventRepository; this.auditingRepository = auditingRepository; - this.baseStripeManager = new BaseStripeManager(configurationManager, configurationRepository, ticketRepository, environment); + this.baseStripeManager = baseStripeManager; this.clockProvider = clockProvider; } @@ -175,7 +198,7 @@ private TransactionInitializationToken buildTokenFromTransaction(Transaction tra if(status.equals("succeeded")) { // the existing PaymentIntent succeeded, so we can confirm the reservation log.info("marking reservation {} as paid, because PaymentIntent reports success", transaction.getReservationId()); - processSuccessfulPaymentIntent(transaction, paymentIntent, ticketReservationRepository.findReservationById(transaction.getReservationId()), purchaseContext); + processSuccessfulPaymentIntent(transaction, paymentIntent, ticketReservationRepository.findReservationById(transaction.getReservationId()), purchaseContext, requestOptions); return errorToken("Reservation status changed", true); } else if(!status.equals(REQUIRES_PAYMENT_METHOD)) { return errorToken("Payment in process", true); @@ -224,9 +247,11 @@ public Optional<TransactionWebhookPayload> parseTransactionPayload(String body, var stripeEvent = Webhook.constructEvent(body, signature, getWebhookSignatureKey(paymentContext.getConfigurationLevel())); String eventType = stripeEvent.getType(); if(eventType.startsWith("charge.")) { - return deserializeObject(stripeEvent).map(obj -> new StripeChargeTransactionWebhookPayload(eventType, (Charge)obj)); + return deserializeObject(stripeEvent, body) + .map(obj -> new StripeChargeTransactionWebhookPayload(eventType, (Charge)obj)); } else if(eventType.startsWith("payment_intent.")) { - return deserializeObject(stripeEvent).map(obj -> new StripePaymentIntentWebhookPayload(eventType, (PaymentIntent)obj)); + return deserializeObject(stripeEvent, body) + .map(obj -> new StripePaymentIntentWebhookPayload(eventType, (PaymentIntent)obj)); } return Optional.empty(); } catch (Exception e) { @@ -235,7 +260,7 @@ public Optional<TransactionWebhookPayload> parseTransactionPayload(String body, } } - private Optional<StripeObject> deserializeObject(com.stripe.model.Event stripeEvent) { + private Optional<StripeObject> deserializeObject(com.stripe.model.Event stripeEvent, String rawJson) { var dataObjectDeserializer = stripeEvent.getDataObjectDeserializer(); var cleanDeserialization = dataObjectDeserializer.getObject(); if(cleanDeserialization.isPresent()) { @@ -243,7 +268,17 @@ private Optional<StripeObject> deserializeObject(com.stripe.model.Event stripeEv } log.warn("unable to deserialize payload. Expected version {}, actual {}, falling back to unsafe deserialization", Stripe.API_VERSION, stripeEvent.getApiVersion()); try { - return Optional.ofNullable(dataObjectDeserializer.deserializeUnsafe()); + return Optional.ofNullable(dataObjectDeserializer.deserializeUnsafe()) + .map(stripeObject -> { + // if the message we received was built with an API version older than 2022-11-15 + // we need to save the raw JSON body to ensure we have all the information to parse the message + // see https://stripe.com/docs/upgrades#2022-11-15 and https://github.com/alfio-event/alf.io/issues/1159 + if (stripeObject.getLastResponse() == null && "2022-11-15".compareTo(stripeEvent.getApiVersion()) > 0) { + log.debug("API version requires raw JSON body. Forcing 'lastResponse' property"); + stripeObject.setLastResponse(new StripeResponse(200, HttpHeaders.of(Map.of()), rawJson)); + } + return stripeObject; + }); } catch(Exception e) { throw new IllegalArgumentException("Cannot deserialize webhook event.", e); } @@ -277,20 +312,15 @@ public PaymentWebhookResult processWebhook(TransactionWebhookPayload payload, Tr } var reservation = optionalReservation.get(); var purchaseContext = paymentContext.getPurchaseContext(); - switch(payload.getType()) { - case PAYMENT_INTENT_CREATED: { - return PaymentWebhookResult.processStarted(buildTokenFromTransaction(transaction, purchaseContext, false)); - } - case PAYMENT_INTENT_SUCCEEDED: { - return processSuccessfulPaymentIntent(transaction, paymentIntent, reservation, purchaseContext); - } - case PAYMENT_INTENT_PAYMENT_FAILED: { - return processFailedPaymentIntent(transaction, reservation, purchaseContext); - } - default: - return PaymentWebhookResult.notRelevant("event is not relevant"); - } - + return switch (payload.getType()) { + case PAYMENT_INTENT_CREATED -> + PaymentWebhookResult.processStarted(buildTokenFromTransaction(transaction, purchaseContext, false)); + case PAYMENT_INTENT_SUCCEEDED -> + processSuccessfulPaymentIntent(transaction, paymentIntent, reservation, purchaseContext, baseStripeManager.options(purchaseContext).orElseThrow()); + case PAYMENT_INTENT_PAYMENT_FAILED -> + processFailedPaymentIntent(transaction, reservation, purchaseContext); + default -> PaymentWebhookResult.notRelevant("event is not relevant"); + }; } catch (Exception e) { log.error("Error while trying to confirm the reservation", e); return PaymentWebhookResult.error("unexpected error"); @@ -314,13 +344,16 @@ private PaymentWebhookResult processFailedPaymentIntent(Transaction transaction, return PaymentWebhookResult.failed("Charge has been reset by Stripe. This is usually caused by a rejection from the customer's bank"); } - private PaymentWebhookResult processSuccessfulPaymentIntent(Transaction transaction, PaymentIntent paymentIntent, TicketReservation reservation, PurchaseContext purchaseContext) { - var charge = paymentIntent.getCharges().getData().get(0); - var chargeId = charge.getId(); - long gtwFee = Optional.ofNullable(charge.getBalanceTransactionObject()).map(BalanceTransaction::getFee).orElse(0L); + private PaymentWebhookResult processSuccessfulPaymentIntent(Transaction transaction, + PaymentIntent paymentIntent, + TicketReservation reservation, + PurchaseContext purchaseContext, + RequestOptions requestOptions) throws StripeException { + var chargeAndFees = retrieveChargeIdAndFees(paymentIntent, requestOptions); + var chargeId = chargeAndFees.chargeId(); transactionRepository.lockByIdForUpdate(transaction.getId());// this serializes int affectedRows = transactionRepository.updateIfStatus(transaction.getId(), chargeId, - transaction.getPaymentId(), purchaseContext.now(clockProvider), transaction.getPlatformFee(), gtwFee, + transaction.getPaymentId(), purchaseContext.now(clockProvider), transaction.getPlatformFee(), chargeAndFees.getFeesOrZero(), Transaction.Status.COMPLETE, Map.of(), Transaction.Status.PENDING); List<Map<String, Object>> modifications = List.of(Map.of("paymentId", chargeId, "paymentMethod", "stripe")); if(affectedRows == 0) { @@ -337,6 +370,38 @@ private PaymentWebhookResult processSuccessfulPaymentIntent(Transaction transact return PaymentWebhookResult.successful(new StripeSCACreditCardToken(transaction.getPaymentId(), chargeId, null)); } + private ChargeIdAndFees retrieveChargeIdAndFees(PaymentIntent paymentIntent, RequestOptions requestOptions) throws StripeException { + String chargeId = paymentIntent.getLatestCharge(); + String balanceTransactionId = null; + long fees = 0L; + if (chargeId == null) { + // compatibility mode for payloads with API version up to 2022-08-01 + var jsonObject = paymentIntent.getRawJsonObject(); + // old structure is paymentIntent -> data -> object -> charges -> data[0] -> id + var chargesContainer = requireNonNull(jsonObject.getAsJsonObject("data") + .getAsJsonObject("object") + .getAsJsonObject("charges"), "data -> object -> charges is null!"); + var latestCharge = requireNonNull(chargesContainer.getAsJsonArray("data").get(0), "charges is empty!") + .getAsJsonObject(); + + chargeId = requireNonNull(latestCharge.get("id"), "charges array is empty!").getAsString(); + if (latestCharge.has("balance_transaction")) { + balanceTransactionId = latestCharge.get("balance_transaction").getAsString(); + } + } + + if (balanceTransactionId == null) { + var charge = baseStripeManager.retrieveCharge(chargeId, requestOptions); + balanceTransactionId = charge.getBalanceTransaction(); + } + + if (balanceTransactionId != null) { + fees = baseStripeManager.retrieveBalanceTransaction(balanceTransactionId, requestOptions).getFee(); + } + + return new ChargeIdAndFees(chargeId, fees); + } + @Override public boolean accept(PaymentMethod paymentMethod, PaymentContext context, TransactionRequest transactionRequest) { return baseStripeManager.accept(paymentMethod, context, OPTIONS_TO_LOAD, this::isConfigurationValid); @@ -420,19 +485,14 @@ public PaymentWebhookResult forceTransactionCheck(TicketReservation reservation, PurchaseContext purchaseContext = paymentContext.getPurchaseContext(); var options = baseStripeManager.options(purchaseContext, builder -> builder.setIdempotencyKey(reservation.getId())).orElseThrow(); var intent = PaymentIntent.retrieve(transaction.getPaymentId(), options); - switch(intent.getStatus()) { - case "processing": - case "requires_action": - case "requires_confirmation": - return PaymentWebhookResult.pending(); - case "succeeded": - return processSuccessfulPaymentIntent(transaction, intent, reservation, purchaseContext); - case REQUIRES_PAYMENT_METHOD: + return switch (intent.getStatus()) { + case "processing", "requires_action", "requires_confirmation" -> PaymentWebhookResult.pending(); + case "succeeded" -> processSuccessfulPaymentIntent(transaction, intent, reservation, purchaseContext, options); + case REQUIRES_PAYMENT_METHOD -> //payment is failed. - return processFailedPaymentIntent(transaction, reservation, purchaseContext); - default: - return null; - } + processFailedPaymentIntent(transaction, reservation, purchaseContext); + default -> null; + }; } catch(Exception ex) { log.error("Error trying to check PaymentIntent status", ex); return PaymentWebhookResult.error("failed"); @@ -463,4 +523,10 @@ public Optional<PaymentContext> detectPaymentContext(String payload) { return Optional.empty(); } } + + private record ChargeIdAndFees(String chargeId, Long fees) { + public long getFeesOrZero() { + return fees != null ? fees : 0L; + } + } } diff --git a/src/main/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilder.java b/src/main/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilder.java index 58528101ec..83909ed1d1 100644 --- a/src/main/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilder.java +++ b/src/main/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilder.java @@ -17,12 +17,10 @@ package alfio.manager.payment.saferpay; import com.google.gson.stream.JsonWriter; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; +import java.io.IOException; import java.io.StringWriter; -@RequiredArgsConstructor public class PaymentPageAssertRequestBuilder { private String customerId; private String requestId; @@ -30,6 +28,11 @@ public class PaymentPageAssertRequestBuilder { private final String token; private final int retryIndicator; + public PaymentPageAssertRequestBuilder(String token, int retryIndicator) { + this.token = token; + this.retryIndicator = retryIndicator; + } + public PaymentPageAssertRequestBuilder addAuthentication(String customerId, String requestId) { this.customerId = customerId; this.requestId = requestId; @@ -37,8 +40,7 @@ public PaymentPageAssertRequestBuilder addAuthentication(String customerId, Stri } // @formatter:off - @SneakyThrows - public String build() { + public String build() throws IOException { var out = new StringWriter(); var requestHeaderBuilder = new RequestHeaderBuilder(customerId, requestId, retryIndicator); try (var writer = new JsonWriter(out)) { diff --git a/src/main/java/alfio/manager/payment/saferpay/PaymentPageInitializeRequestBuilder.java b/src/main/java/alfio/manager/payment/saferpay/PaymentPageInitializeRequestBuilder.java index 468b8e56a7..449ba719b9 100644 --- a/src/main/java/alfio/manager/payment/saferpay/PaymentPageInitializeRequestBuilder.java +++ b/src/main/java/alfio/manager/payment/saferpay/PaymentPageInitializeRequestBuilder.java @@ -19,10 +19,10 @@ import alfio.manager.payment.PaymentSpecification; import alfio.model.transaction.PaymentMethod; import com.google.gson.stream.JsonWriter; -import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.springframework.web.util.UriComponentsBuilder; +import java.io.IOException; import java.io.StringWriter; import java.util.Set; @@ -94,8 +94,7 @@ public PaymentPageInitializeRequestBuilder addOrderInformation(String orderId, return this; } - @SneakyThrows - public String build() { + public String build() throws IOException { var out = new StringWriter(); var requestHeaderBuilder = new RequestHeaderBuilder(customerId, requestId, retryIndicator); try (var writer = new JsonWriter(out)) { @@ -123,8 +122,7 @@ public String build() { return out.toString(); } - @SneakyThrows - private JsonWriter addPaymentMethods(JsonWriter writer) { + private JsonWriter addPaymentMethods(JsonWriter writer) throws IOException { var array = writer.name("PaymentMethods").beginArray(); for (String method : SUPPORTED_METHODS) { array.value(method); diff --git a/src/main/java/alfio/manager/payment/saferpay/RequestHeaderBuilder.java b/src/main/java/alfio/manager/payment/saferpay/RequestHeaderBuilder.java index faf3799c44..9098675ecf 100644 --- a/src/main/java/alfio/manager/payment/saferpay/RequestHeaderBuilder.java +++ b/src/main/java/alfio/manager/payment/saferpay/RequestHeaderBuilder.java @@ -17,23 +17,18 @@ package alfio.manager.payment.saferpay; import com.google.gson.stream.JsonWriter; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -@RequiredArgsConstructor -class RequestHeaderBuilder { +import java.io.IOException; + +record RequestHeaderBuilder(String customerId, String requestId, Integer retryIndicator) { private static final String SPEC_VERSION = "1.18"; - private final String customerId; - private final String requestId; - private final Integer retryIndicator; - @SneakyThrows - JsonWriter appendTo(JsonWriter writer) { + JsonWriter appendTo(JsonWriter writer) throws IOException { writer.name("RequestHeader").beginObject() // .name("SpecVersion").value(SPEC_VERSION) // .name("CustomerId").value(customerId) // .name("RequestId").value(requestId); - if(retryIndicator != null) { + if (retryIndicator != null) { writer.name("RetryIndicator").value(retryIndicator); } return writer.endObject(); diff --git a/src/main/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilder.java b/src/main/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilder.java index 2ceac4e6c6..b7a0132167 100644 --- a/src/main/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilder.java +++ b/src/main/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilder.java @@ -17,13 +17,10 @@ package alfio.manager.payment.saferpay; import com.google.gson.stream.JsonWriter; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import java.io.IOException; import java.io.StringWriter; -@RequiredArgsConstructor public class TransactionCaptureRequestBuilder { private final String token; @@ -31,14 +28,18 @@ public class TransactionCaptureRequestBuilder { private String customerId; private String requestId; + public TransactionCaptureRequestBuilder(String token, int retryIndicator) { + this.token = token; + this.retryIndicator = retryIndicator; + } + public TransactionCaptureRequestBuilder addAuthentication(String customerId, String requestId) { this.customerId = customerId; this.requestId = requestId; return this; } // @formatter:off - @SneakyThrows - public String build() { + public String build() throws IOException { return buildRequest(customerId, requestId, retryIndicator, token); } diff --git a/src/main/java/alfio/manager/payment/saferpay/TransactionInquireRequestBuilder.java b/src/main/java/alfio/manager/payment/saferpay/TransactionInquireRequestBuilder.java index fa196174aa..034dfa09c6 100644 --- a/src/main/java/alfio/manager/payment/saferpay/TransactionInquireRequestBuilder.java +++ b/src/main/java/alfio/manager/payment/saferpay/TransactionInquireRequestBuilder.java @@ -16,16 +16,19 @@ */ package alfio.manager.payment.saferpay; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; +import java.io.IOException; -@RequiredArgsConstructor public class TransactionInquireRequestBuilder { private final String transactionId; private final int retryIndicator; private String customerId; private String requestId; + public TransactionInquireRequestBuilder(String transactionId, int retryIndicator) { + this.transactionId = transactionId; + this.retryIndicator = retryIndicator; + } + public TransactionInquireRequestBuilder addAuthentication(String customerId, String requestId) { this.customerId = customerId; this.requestId = requestId; @@ -33,8 +36,7 @@ public TransactionInquireRequestBuilder addAuthentication(String customerId, Str } // @formatter:off - @SneakyThrows - public String build() { + public String build() throws IOException { return TransactionCaptureRequestBuilder.buildRequest(customerId, requestId, retryIndicator, transactionId); } } diff --git a/src/main/java/alfio/manager/payment/saferpay/TransactionRefundBuilder.java b/src/main/java/alfio/manager/payment/saferpay/TransactionRefundBuilder.java index d0c9e935b9..24be9bcd08 100644 --- a/src/main/java/alfio/manager/payment/saferpay/TransactionRefundBuilder.java +++ b/src/main/java/alfio/manager/payment/saferpay/TransactionRefundBuilder.java @@ -17,18 +17,21 @@ package alfio.manager.payment.saferpay; import com.google.gson.stream.JsonWriter; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; +import java.io.IOException; import java.io.StringWriter; -@RequiredArgsConstructor public class TransactionRefundBuilder { private final String captureId; private final int retryIndicator; private String customerId; private String requestId; + public TransactionRefundBuilder(String captureId, int retryIndicator) { + this.captureId = captureId; + this.retryIndicator = retryIndicator; + } + public TransactionRefundBuilder addAuthentication(String customerId, String requestId) { this.customerId = customerId; this.requestId = requestId; @@ -36,8 +39,7 @@ public TransactionRefundBuilder addAuthentication(String customerId, String requ } // @formatter:off - @SneakyThrows - public String build(String amountToRefund, String currencyCode) { + public String build(String amountToRefund, String currencyCode) throws IOException { var out = new StringWriter(); var requestHeaderBuilder = new RequestHeaderBuilder(customerId, requestId, retryIndicator); try (var writer = new JsonWriter(out)) { diff --git a/src/main/java/alfio/manager/support/AdditionalServiceInfo.java b/src/main/java/alfio/manager/support/AdditionalServiceInfo.java index 5280b59ea7..7c0cabaf99 100644 --- a/src/main/java/alfio/manager/support/AdditionalServiceInfo.java +++ b/src/main/java/alfio/manager/support/AdditionalServiceInfo.java @@ -18,14 +18,20 @@ import alfio.model.TicketFieldValueForAdditionalService; import lombok.Getter; -import lombok.RequiredArgsConstructor; import java.util.List; @Getter -@RequiredArgsConstructor public class AdditionalServiceInfo { private final String name; private final int count; private final List<TicketFieldValueForAdditionalService> fields; + + public AdditionalServiceInfo(String name, + int count, + List<TicketFieldValueForAdditionalService> fields) { + this.name = name; + this.count = count; + this.fields = fields; + } } diff --git a/src/main/java/alfio/manager/support/ConfirmationEmailConfiguration.java b/src/main/java/alfio/manager/support/ConfirmationEmailConfiguration.java index c4063f58b2..150258cf86 100644 --- a/src/main/java/alfio/manager/support/ConfirmationEmailConfiguration.java +++ b/src/main/java/alfio/manager/support/ConfirmationEmailConfiguration.java @@ -18,18 +18,26 @@ import alfio.manager.system.Mailer; import alfio.util.TemplateResource; -import lombok.AllArgsConstructor; import java.util.List; import java.util.Map; -@AllArgsConstructor public class ConfirmationEmailConfiguration { private final TemplateResource templateResource; private final String emailAddress; private final Map<String, Object> model; private final List<Mailer.Attachment> attachments; + public ConfirmationEmailConfiguration(TemplateResource templateResource, + String emailAddress, + Map<String, Object> model, + List<Mailer.Attachment> attachments) { + this.templateResource = templateResource; + this.emailAddress = emailAddress; + this.model = model; + this.attachments = attachments; + } + public TemplateResource getTemplateResource() { return templateResource; } diff --git a/src/main/java/alfio/manager/support/CustomMessageManager.java b/src/main/java/alfio/manager/support/CustomMessageManager.java index 6172bd9f9c..9a4d428841 100644 --- a/src/main/java/alfio/manager/support/CustomMessageManager.java +++ b/src/main/java/alfio/manager/support/CustomMessageManager.java @@ -25,18 +25,15 @@ import alfio.manager.system.Mailer; import alfio.model.*; import alfio.model.modification.MessageModification; +import alfio.model.system.ConfigurationKeys; import alfio.model.user.Organization; import alfio.repository.EventRepository; import alfio.repository.TicketCategoryRepository; import alfio.repository.TicketRepository; -import alfio.util.EventUtil; -import alfio.util.Json; -import alfio.util.RenderedTemplate; -import alfio.util.TemplateManager; +import alfio.util.*; import alfio.util.checkin.TicketCheckInUtil; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Triple; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; @@ -50,8 +47,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; @Component -@AllArgsConstructor -@Log4j2 public class CustomMessageManager { private final TemplateManager templateManager; @@ -66,6 +61,28 @@ public class CustomMessageManager { private final ExtensionManager extensionManager; private final EventRepository eventRepository; + public CustomMessageManager(TemplateManager templateManager, + EventManager eventManager, + TicketRepository ticketRepository, + TicketReservationManager ticketReservationManager, + NotificationManager notificationManager, + TicketCategoryRepository ticketCategoryRepository, + ConfigurationManager configurationManager, + MessageSourceManager messageSourceManager, + ExtensionManager extensionManager, + EventRepository eventRepository) { + this.templateManager = templateManager; + this.eventManager = eventManager; + this.ticketRepository = ticketRepository; + this.ticketReservationManager = ticketReservationManager; + this.notificationManager = notificationManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.configurationManager = configurationManager; + this.messageSourceManager = messageSourceManager; + this.extensionManager = extensionManager; + this.eventRepository = eventRepository; + } + public Map<String, Object> generatePreview(String eventName, Optional<Integer> categoryId, List<MessageModification> input, String username) { Map<String, Object> result = new HashMap<>(); Event event = eventManager.getSingleEvent(eventName, username); @@ -81,6 +98,11 @@ public void sendMessages(String eventName, Optional<Integer> categoryId, List<Me Organization organization = eventManager.loadOrganizer(event, username); Map<String, List<MessageModification>> byLanguage = input.stream().collect(Collectors.groupingBy(m -> m.getLocale().getLanguage())); var categoriesById = ticketCategoryRepository.findByEventIdAsMap(event.getId()); + var configuration = configurationManager.getFor(EnumSet.of(ConfigurationKeys.BASE_URL, + ConfigurationKeys.ENABLE_WALLET, ConfigurationKeys.ENABLE_PASS, ConfigurationKeys.ENABLE_HTML_EMAILS), event.getConfigurationLevel()); + var baseUrl = configuration.get(ConfigurationKeys.BASE_URL).getRequiredValue(); + boolean googleWalletEnabled = configuration.get(ConfigurationKeys.ENABLE_WALLET).getValueAsBooleanOrDefault(); + boolean appleWalletEnabled = configuration.get(ConfigurationKeys.ENABLE_PASS).getValueAsBooleanOrDefault(); sendMessagesExecutor.execute(() -> { var messageSource = messageSourceManager.getMessageSourceFor(event); @@ -97,8 +119,9 @@ public void sendMessages(String eventName, Optional<Integer> categoryId, List<Me model.addAttribute("organizationEmail", organization.getEmail()); model.addAttribute("reservationURL", ticketReservationManager.reservationUrl(t.getTicketsReservationId(), event)); model.addAttribute("reservationID", ticketReservationManager.getShortReservationID(event, t.getTicketsReservationId())); - model.addAttribute("ticketURL", ticketReservationManager.ticketUpdateUrl(event, t.getUuid())); + model.addAttribute("ticketURL", ReservationUtil.ticketUpdateUrl(event, t, configurationManager)); model.addAttribute("ticketID", t.getUuid()); + model.addAttribute("ticket", t); return Triple.of(t, t.getEmail(), model); }) .forEach(triple -> { @@ -112,8 +135,9 @@ public void sendMessages(String eventName, Optional<Integer> categoryId, List<Me if(m.isAttachTicket()) { var optionalReservation = ticketReservationManager.findById(ticket.getTicketsReservationId()); var optionalTicketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId()); + boolean onlineTicket = optionalTicketCategory.isPresent() && EventUtil.isAccessOnline(optionalTicketCategory.get(), event); - if(optionalReservation.isPresent() && optionalTicketCategory.isPresent() && EventUtil.isAccessOnline(optionalTicketCategory.get(), event)) { + if(optionalReservation.isPresent() && onlineTicket) { var onlineCheckInModel = new HashMap<>(TicketCheckInUtil.getOnlineCheckInInfo( extensionManager, eventRepository, @@ -133,10 +157,23 @@ public void sendMessages(String eventName, Optional<Integer> categoryId, List<Me text.append(notificationManager.buildOnlineCheckInText(onlineCheckInModel, Locale.forLanguageTag(ticket.getUserLanguage()), messageSource)); templateModel.putAll(onlineCheckInModel); } else if(optionalReservation.isPresent() && optionalTicketCategory.isPresent()) { - attachments.add(generateTicketAttachment(ticket, optionalReservation.get(), optionalTicketCategory.get(), organization)); + boolean htmlEmailsEnabled = configuration.get(ConfigurationKeys.ENABLE_HTML_EMAILS).getValueAsBooleanOrDefault(); + attachments.add(generateTicketAttachment(ticket, optionalReservation.get(), optionalTicketCategory.get(), organization, htmlEmailsEnabled)); } + templateModel.put("googleWalletEnabled", googleWalletEnabled && !onlineTicket); + templateModel.put("appleWalletEnabled", appleWalletEnabled && !onlineTicket); + templateModel.put("walletEnabled", (googleWalletEnabled || appleWalletEnabled) && !onlineTicket); + } else { + // ticket attachment was not requested. Do not display wallet + templateModel.put("googleWalletEnabled", false); + templateModel.put("appleWalletEnabled", false); + templateModel.put("walletEnabled", false); } - notificationManager.sendSimpleEmail(event, ticket.getTicketsReservationId(), triple.getMiddle(), subject, () -> RenderedTemplate.plaintext(text.toString(), templateModel), attachments); + templateModel.put("message", text); + templateModel.put("event", event); + templateModel.put("baseUrl", baseUrl); + notificationManager.sendSimpleEmail(event, ticket.getTicketsReservationId(), triple.getMiddle(), subject, + () -> templateManager.renderTemplate(event, TemplateResource.CUSTOM_MESSAGE, templateModel, Locale.forLanguageTag(ticket.getUserLanguage())), attachments); }); }); @@ -159,9 +196,16 @@ private List<MessageModification> preview(Event event, List<MessageModification> .collect(Collectors.toList()); } - public static Mailer.Attachment generateTicketAttachment(Ticket ticket, TicketReservation reservation, TicketCategory ticketCategory, Organization organization) { + public static Mailer.Attachment generateTicketAttachment(Ticket ticket, + TicketReservation reservation, + TicketCategory ticketCategory, + Organization organization, + boolean htmlEmailEnabled) { Map<String, String> model = getModelForTicket(ticket, reservation, ticketCategory, organization); - return new Mailer.Attachment("ticket-" + ticket.getUuid() + ".pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.TICKET_PDF); + if (htmlEmailEnabled) { + model.put(Mailer.SKIP_PASSBOOK, "true"); + } + return new Mailer.Attachment("ticket-" + ticket.getUuid() + ".pdf", null, MediaType.APPLICATION_PDF_VALUE, model, Mailer.AttachmentIdentifier.TICKET_PDF); } private static Map<String, String> getModelForTicket(Ticket ticket, TicketReservation reservation, TicketCategory ticketCategory, Organization organization) { diff --git a/src/main/java/alfio/manager/support/IncompatibleStateException.java b/src/main/java/alfio/manager/support/IncompatibleStateException.java new file mode 100644 index 0000000000..dd56b5e3bd --- /dev/null +++ b/src/main/java/alfio/manager/support/IncompatibleStateException.java @@ -0,0 +1,24 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support; + +public class IncompatibleStateException extends RuntimeException { + + public IncompatibleStateException(String message) { + super(message); + } +} diff --git a/src/main/java/alfio/manager/support/PaymentResult.java b/src/main/java/alfio/manager/support/PaymentResult.java index eb12e41c88..c727212db5 100644 --- a/src/main/java/alfio/manager/support/PaymentResult.java +++ b/src/main/java/alfio/manager/support/PaymentResult.java @@ -17,13 +17,11 @@ package alfio.manager.support; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.EqualsAndHashCode; -import lombok.ToString; +import org.apache.commons.lang3.builder.ToStringBuilder; +import java.util.Objects; import java.util.Optional; -@EqualsAndHashCode -@ToString public final class PaymentResult { public enum Type { SUCCESSFUL, INITIALIZED, PENDING, REDIRECT, FAILED } @@ -103,4 +101,27 @@ public static PaymentResult initialized(String gatewayId) { public static PaymentResult failed( String errorCode) { return new PaymentResult(Type.FAILED).setErrorCode( errorCode ); } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("type", type) + .append("gatewayId", gatewayId) + .append("errorCode", errorCode) + .append("redirectUrl", redirectUrl) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PaymentResult that = (PaymentResult) o; + return type == that.type && Objects.equals(gatewayId, that.gatewayId) && Objects.equals(errorCode, that.errorCode) && Objects.equals(redirectUrl, that.redirectUrl); + } + + @Override + public int hashCode() { + return Objects.hash(type, gatewayId, errorCode, redirectUrl); + } } diff --git a/src/main/java/alfio/manager/support/PaymentWebhookResult.java b/src/main/java/alfio/manager/support/PaymentWebhookResult.java index d00dbe4f9f..17a7e1c3ea 100644 --- a/src/main/java/alfio/manager/support/PaymentWebhookResult.java +++ b/src/main/java/alfio/manager/support/PaymentWebhookResult.java @@ -17,10 +17,8 @@ package alfio.manager.support; import alfio.model.transaction.PaymentToken; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class PaymentWebhookResult { @@ -39,6 +37,13 @@ public enum Type { private final String reason; private final String redirectUrl; + public PaymentWebhookResult(Type type, PaymentToken paymentToken, String reason, String redirectUrl) { + this.type = type; + this.paymentToken = paymentToken; + this.reason = reason; + this.redirectUrl = redirectUrl; + } + public boolean isSuccessful() { return type == Type.SUCCESSFUL; } diff --git a/src/main/java/alfio/manager/support/RefundPriceContainer.java b/src/main/java/alfio/manager/support/RefundPriceContainer.java index 69c48e8c4b..b1015ea8fa 100644 --- a/src/main/java/alfio/manager/support/RefundPriceContainer.java +++ b/src/main/java/alfio/manager/support/RefundPriceContainer.java @@ -60,20 +60,12 @@ public VatStatus getVatStatus() { } private static VatStatus determineRefundVatStatus(VatStatus reservationVatStatus) { - switch (reservationVatStatus) { - case NOT_INCLUDED_NOT_CHARGED: - case INCLUDED_NOT_CHARGED: - return VatStatus.NOT_INCLUDED_NOT_CHARGED; - case NOT_INCLUDED_EXEMPT: - case INCLUDED_EXEMPT: - return VatStatus.NOT_INCLUDED_EXEMPT; - case INCLUDED: - case NOT_INCLUDED: - return VatStatus.INCLUDED; - case NONE: - return VatStatus.NONE; - default: - throw new IllegalStateException("Vat status "+reservationVatStatus+ " not mapped"); - } + return switch (reservationVatStatus) { + case NOT_INCLUDED_NOT_CHARGED, INCLUDED_NOT_CHARGED -> VatStatus.NOT_INCLUDED_NOT_CHARGED; + case NOT_INCLUDED_EXEMPT, INCLUDED_EXEMPT -> VatStatus.NOT_INCLUDED_EXEMPT; + case INCLUDED, NOT_INCLUDED -> VatStatus.INCLUDED; + case NONE -> VatStatus.NONE; + default -> throw new IllegalStateException("Vat status " + reservationVatStatus + " not mapped"); + }; } } diff --git a/src/main/java/alfio/manager/support/RetryFinalizeReservation.java b/src/main/java/alfio/manager/support/RetryFinalizeReservation.java new file mode 100644 index 0000000000..ffb5004d6a --- /dev/null +++ b/src/main/java/alfio/manager/support/RetryFinalizeReservation.java @@ -0,0 +1,99 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support; + +import alfio.model.TicketReservation.TicketReservationStatus; +import alfio.model.system.command.FinalizeReservation; +import alfio.model.transaction.PaymentProxy; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class RetryFinalizeReservation { + + private final String reservationId; + private final PaymentProxy paymentProxy; + private final boolean sendReservationConfirmationEmail; + private final boolean sendTickets; + private final String username; + private final boolean tcAccepted; + private final boolean privacyPolicyAccepted; + private final TicketReservationStatus originalStatus; + + @JsonCreator + public RetryFinalizeReservation(@JsonProperty("reservationId") String reservationId, + @JsonProperty("paymentProxy") PaymentProxy paymentProxy, + @JsonProperty("sendReservationConfirmationEmail") boolean sendReservationConfirmationEmail, + @JsonProperty("sendTickets") boolean sendTickets, + @JsonProperty("username") String username, + @JsonProperty("tcAccepted") boolean tcAccepted, + @JsonProperty("privacyPolicyAccepted") boolean privacyPolicyAccepted, + @JsonProperty("originalStatus") TicketReservationStatus originalStatus) { + this.reservationId = reservationId; + this.paymentProxy = paymentProxy; + this.sendReservationConfirmationEmail = sendReservationConfirmationEmail; + this.sendTickets = sendTickets; + this.username = username; + this.tcAccepted = tcAccepted; + this.privacyPolicyAccepted = privacyPolicyAccepted; + this.originalStatus = originalStatus; + } + + public String getReservationId() { + return reservationId; + } + + public PaymentProxy getPaymentProxy() { + return paymentProxy; + } + + public boolean isSendReservationConfirmationEmail() { + return sendReservationConfirmationEmail; + } + + public boolean isSendTickets() { + return sendTickets; + } + + public String getUsername() { + return username; + } + + public boolean isTcAccepted() { + return tcAccepted; + } + + public boolean isPrivacyPolicyAccepted() { + return privacyPolicyAccepted; + } + + public TicketReservationStatus getOriginalStatus() { + return originalStatus; + } + + public static RetryFinalizeReservation fromFinalizeReservation(FinalizeReservation finalizeReservation) { + var paymentSpecification = finalizeReservation.getPaymentSpecification(); + return new RetryFinalizeReservation(paymentSpecification.getReservationId(), + finalizeReservation.getPaymentProxy(), + finalizeReservation.isSendReservationConfirmationEmail(), + finalizeReservation.isSendTickets(), + finalizeReservation.getUsername(), + paymentSpecification.isTcAccepted(), + paymentSpecification.isPrivacyAccepted(), + finalizeReservation.getOriginalStatus() + ); + } +} diff --git a/src/main/java/alfio/manager/support/TemplateGenerator.java b/src/main/java/alfio/manager/support/TemplateGenerator.java index 680c793547..a0a256f89b 100644 --- a/src/main/java/alfio/manager/support/TemplateGenerator.java +++ b/src/main/java/alfio/manager/support/TemplateGenerator.java @@ -17,7 +17,7 @@ package alfio.manager.support; import alfio.util.RenderedTemplate; - +@FunctionalInterface public interface TemplateGenerator { RenderedTemplate generate(); } diff --git a/src/main/java/alfio/manager/support/extension/ExtensionEvent.java b/src/main/java/alfio/manager/support/extension/ExtensionEvent.java index 7d5e6d9585..c668fa83ef 100644 --- a/src/main/java/alfio/manager/support/extension/ExtensionEvent.java +++ b/src/main/java/alfio/manager/support/extension/ExtensionEvent.java @@ -29,6 +29,7 @@ public enum ExtensionEvent { CREDIT_NOTE_GENERATION, CREDIT_NOTE_GENERATED, TAX_ID_NUMBER_VALIDATION, + CUSTOM_TAX_POLICY_APPLICATION, RESERVATION_VALIDATION, EVENT_METADATA_UPDATE, // @@ -49,6 +50,8 @@ public enum ExtensionEvent { DYNAMIC_DISCOUNT_APPLICATION, + SUBSCRIPTION_ASSIGNED_GENERATE_METADATA, + ONLINE_CHECK_IN_REDIRECT, CUSTOM_ONLINE_JOIN_URL, diff --git a/src/main/java/alfio/manager/support/reservation/CannotProceedWithPayment.java b/src/main/java/alfio/manager/support/reservation/CannotProceedWithPayment.java new file mode 100644 index 0000000000..77cdf62fac --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/CannotProceedWithPayment.java @@ -0,0 +1,23 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +public class CannotProceedWithPayment extends RuntimeException { + public CannotProceedWithPayment(String message) { + super(message); + } +} diff --git a/src/main/java/alfio/manager/support/reservation/InvalidSpecialPriceTokenException.java b/src/main/java/alfio/manager/support/reservation/InvalidSpecialPriceTokenException.java new file mode 100644 index 0000000000..0a8cb122bb --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/InvalidSpecialPriceTokenException.java @@ -0,0 +1,21 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +public class InvalidSpecialPriceTokenException extends RuntimeException { + +} diff --git a/src/main/java/alfio/manager/support/reservation/MissingSpecialPriceTokenException.java b/src/main/java/alfio/manager/support/reservation/MissingSpecialPriceTokenException.java new file mode 100644 index 0000000000..b7af6448dd --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/MissingSpecialPriceTokenException.java @@ -0,0 +1,20 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +public class MissingSpecialPriceTokenException extends RuntimeException { +} diff --git a/src/main/java/alfio/manager/support/reservation/NotEnoughTicketsException.java b/src/main/java/alfio/manager/support/reservation/NotEnoughTicketsException.java new file mode 100644 index 0000000000..533c2d8e60 --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/NotEnoughTicketsException.java @@ -0,0 +1,21 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +public class NotEnoughTicketsException extends RuntimeException { + +} diff --git a/src/main/java/alfio/manager/support/reservation/OrderSummaryGenerator.java b/src/main/java/alfio/manager/support/reservation/OrderSummaryGenerator.java new file mode 100644 index 0000000000..c1b163e736 --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/OrderSummaryGenerator.java @@ -0,0 +1,312 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +import alfio.manager.PaymentManager; +import alfio.manager.i18n.MessageSourceManager; +import alfio.model.*; +import alfio.model.decorator.AdditionalServiceItemPriceContainer; +import alfio.model.decorator.TicketPriceContainer; +import alfio.model.subscription.Subscription; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.subscription.SubscriptionPriceContainer; +import alfio.model.transaction.PaymentProxy; +import alfio.repository.*; +import alfio.util.LocaleUtil; +import alfio.util.MonetaryUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static alfio.manager.support.reservation.ReservationCostCalculator.totalReservationCostWithVAT; +import static alfio.model.TicketReservation.TicketReservationStatus.DEFERRED_OFFLINE_PAYMENT; +import static alfio.util.MonetaryUtil.formatCents; +import static alfio.util.MonetaryUtil.formatUnit; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +@Component +public class OrderSummaryGenerator { + + private static final Logger log = LoggerFactory.getLogger(OrderSummaryGenerator.class); + private final TicketReservationRepository ticketReservationRepository; + private final AuditingRepository auditingRepository; + private final PaymentManager paymentManager; + private final TicketCategoryRepository ticketCategoryRepository; + private final AdditionalServiceTextRepository additionalServiceTextRepository; + private final SubscriptionRepository subscriptionRepository; + private final TicketRepository ticketRepository; + private final MessageSourceManager messageSourceManager; + private final ReservationCostCalculator reservationCostCalculator; + + public OrderSummaryGenerator(TicketReservationRepository ticketReservationRepository, + AuditingRepository auditingRepository, + PaymentManager paymentManager, + TicketCategoryRepository ticketCategoryRepository, + AdditionalServiceTextRepository additionalServiceTextRepository, + SubscriptionRepository subscriptionRepository, + TicketRepository ticketRepository, + MessageSourceManager messageSourceManager, + ReservationCostCalculator reservationCostCalculator) { + this.ticketReservationRepository = ticketReservationRepository; + this.auditingRepository = auditingRepository; + this.paymentManager = paymentManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.additionalServiceTextRepository = additionalServiceTextRepository; + this.subscriptionRepository = subscriptionRepository; + this.ticketRepository = ticketRepository; + this.messageSourceManager = messageSourceManager; + this.reservationCostCalculator = reservationCostCalculator; + } + + public OrderSummary orderSummaryForReservationId(String reservationId, PurchaseContext purchaseContext) { + TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId); + return orderSummaryForReservation(reservation, purchaseContext); + } + public OrderSummary orderSummaryForReservation(TicketReservation reservation, PurchaseContext context) { + var totalPriceAndDiscount = reservationCostCalculator.totalReservationCostWithVAT(reservation); + TotalPrice reservationCost = totalPriceAndDiscount.getLeft(); + PromoCodeDiscount discount = totalPriceAndDiscount.getRight().orElse(null); + // + boolean free = reservationCost.priceWithVAT() == 0; + String refundedAmount = null; + + boolean hasRefund = auditingRepository.countAuditsOfTypeForReservation(reservation.getId(), Audit.EventType.REFUND) > 0; + + if(hasRefund) { + refundedAmount = paymentManager.getInfo(reservation, context).paymentInformation().getRefundedAmount(); + } + + var currencyCode = reservation.getCurrencyCode(); + return new OrderSummary(reservationCost, + extractSummary(reservation.getId(), reservation.getVatStatus(), context, LocaleUtil.forLanguageTag(reservation.getUserLanguage()), discount, reservationCost), + free, + formatCents(reservationCost.priceWithVAT(), currencyCode), + formatCents(reservationCost.VAT(), currencyCode), + reservation.getStatus() == TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT, + reservation.getStatus() == DEFERRED_OFFLINE_PAYMENT, + reservation.getPaymentMethod() == PaymentProxy.ON_SITE, + Optional.ofNullable(context.getVat()).map(p -> MonetaryUtil.formatCents(MonetaryUtil.unitToCents(p, currencyCode), currencyCode)).orElse(null), + reservation.getVatStatus(), + refundedAmount); + } + + public OrderSummary orderSummaryForCreditNote(TicketReservation reservation, PurchaseContext purchaseContext, List<Ticket> removedTickets) { + var totalPriceAndDiscount = totalReservationCostWithVAT(null, purchaseContext, reservation, removedTickets, List.of(), List.of(), Optional.empty()); + TotalPrice reservationCost = totalPriceAndDiscount.getLeft(); + // + boolean free = reservationCost.priceWithVAT() == 0; + + var currencyCode = reservation.getCurrencyCode(); + return new OrderSummary(reservationCost, + extractSummary(reservation.getVatStatus(), purchaseContext, LocaleUtil.forLanguageTag(reservation.getUserLanguage()), null, reservationCost, removedTickets, Stream.empty(), subscriptionRepository.findSubscriptionsByReservationId(reservation.getId())), + free, + formatCents(reservationCost.priceWithVAT(), currencyCode), + formatCents(reservationCost.VAT(), currencyCode), + reservation.getStatus() == TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT, + reservation.getStatus() == DEFERRED_OFFLINE_PAYMENT, + reservation.getPaymentMethod() == PaymentProxy.ON_SITE, + Optional.ofNullable(purchaseContext.getVat()).map(p -> MonetaryUtil.formatCents(MonetaryUtil.unitToCents(p, currencyCode), currencyCode)).orElse(null), + reservation.getVatStatus(), + null); + } + + List<SummaryRow> extractSummary(PriceContainer.VatStatus reservationVatStatus, + PurchaseContext purchaseContext, + Locale locale, + PromoCodeDiscount promoCodeDiscount, + TotalPrice reservationCost, + List<Ticket> ticketsToInclude, + Stream<Pair<AdditionalService, List<AdditionalServiceItem>>> additionalServicesToInclude, + List<Subscription> subscriptionsToInclude) { + log.trace("extract summary subscriptionsToInclude {}", subscriptionsToInclude); + List<SummaryRow> summary = new ArrayList<>(); + var currencyCode = reservationCost.currencyCode(); + List<TicketPriceContainer> tickets = ticketsToInclude.stream() + .map(t -> TicketPriceContainer.from(t, reservationVatStatus, purchaseContext.getVat(), purchaseContext.getVatStatus(), promoCodeDiscount)).collect(toList()); + purchaseContext.event().ifPresent(event -> { + boolean multipleTaxRates = tickets.stream().map(TicketPriceContainer::getVatStatus).collect(Collectors.toSet()).size() > 1; + var ticketsByCategory = tickets.stream() + .collect(Collectors.groupingBy(TicketPriceContainer::getCategoryId)); + List<Map.Entry<Integer, List<TicketPriceContainer>>> sorted; + if (multipleTaxRates) { + sorted = ticketsByCategory + .entrySet() + .stream() + .sorted(Comparator.comparing((Map.Entry<Integer, List<TicketPriceContainer>> e) -> e.getValue().get(0).getVatStatus()).reversed()) + .collect(Collectors.toList()); + } else { + sorted = new ArrayList<>(ticketsByCategory.entrySet()); + } + Map<Integer, TicketCategory> categoriesById; + + if(ticketsByCategory.isEmpty()) { + categoriesById = Map.of(); + } else { + categoriesById = ticketCategoryRepository.getByIdsAndActive(ticketsByCategory.keySet(), event.getId()) + .stream() + .collect(Collectors.toMap(TicketCategory::getId, Function.identity())); + } + + for (var categoryWithTickets : sorted) { + var categoryTickets = categoryWithTickets.getValue(); + final int subTotal = categoryTickets.stream().mapToInt(TicketPriceContainer::getSummarySrcPriceCts).sum(); + final int subTotalBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(categoryTickets); + var firstTicket = categoryTickets.get(0); + final int ticketPriceCts = firstTicket.getSummarySrcPriceCts(); + final int priceBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(singletonList(firstTicket)); + String categoryName = categoriesById.get(categoryWithTickets.getKey()).getName(); + var ticketVatStatus = firstTicket.getVatStatus(); + summary.add(new SummaryRow(categoryName, formatCents(ticketPriceCts, currencyCode), formatCents(priceBeforeVat, currencyCode), categoryTickets.size(), formatCents(subTotal, currencyCode), formatCents(subTotalBeforeVat, currencyCode), subTotal, SummaryRow.SummaryType.TICKET, null, ticketVatStatus)); + if (PriceContainer.VatStatus.isVatExempt(ticketVatStatus) && ticketVatStatus != reservationVatStatus) { + summary.add(new SummaryRow(null, + "", + "", + 0, + formatCents(0, currencyCode, true), + formatCents(0, currencyCode, true), + 0, + SummaryRow.SummaryType.TAX_DETAIL, + "0", ticketVatStatus)); + } + } + }); + + summary.addAll(additionalServicesToInclude + .map(entry -> { + String language = locale.getLanguage(); + AdditionalServiceText title = additionalServiceTextRepository.findBestMatchByLocaleAndType(entry.getKey().getId(), language, AdditionalServiceText.TextType.TITLE); + if(!title.locale().equals(language) || title.id() == -1) { + log.debug("additional service {}: title not found for locale {}", title.additionalServiceId(), language); + } + List<AdditionalServiceItemPriceContainer> prices = generateASIPriceContainers(purchaseContext, null).apply(entry).collect(toList()); + AdditionalServiceItemPriceContainer first = prices.get(0); + final int subtotal = prices.stream().mapToInt(AdditionalServiceItemPriceContainer::getSrcPriceCts).sum(); + final int subtotalBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(prices); + return new SummaryRow(title.value(), formatCents(first.getSrcPriceCts(), currencyCode), formatCents(SummaryPriceContainer.getSummaryPriceBeforeVatCts(singletonList(first)), currencyCode), prices.size(), formatCents(subtotal, currencyCode), formatCents(subtotalBeforeVat, currencyCode), subtotal, SummaryRow.SummaryType.ADDITIONAL_SERVICE, null, first.getVatStatus()); + }).toList()); + + Optional.ofNullable(promoCodeDiscount).ifPresent(promo -> { + String formattedSingleAmount = "-" + (PromoCodeDiscount.DiscountType.isFixedAmount(promo.getDiscountType()) ? formatCents(promo.getDiscountAmount(), currencyCode) : (promo.getDiscountAmount()+"%")); + summary.add(new SummaryRow(formatPromoCode(promo, ticketsToInclude, locale, purchaseContext), + formattedSingleAmount, + formattedSingleAmount, + reservationCost.discountAppliedCount(), + formatCents(reservationCost.discount(), currencyCode), formatCents(reservationCost.discount(), currencyCode), reservationCost.discount(), + promo.isDynamic() ? SummaryRow.SummaryType.DYNAMIC_DISCOUNT : SummaryRow.SummaryType.PROMOTION_CODE, + null, reservationVatStatus)); + }); + // + if(purchaseContext instanceof SubscriptionDescriptor) { + if(!subscriptionsToInclude.isEmpty()) { + var subscription = subscriptionsToInclude.get(0); + var priceContainer = new SubscriptionPriceContainer(subscription, promoCodeDiscount, (SubscriptionDescriptor) purchaseContext); + var priceBeforeVat = formatUnit(priceContainer.getNetPrice(), currencyCode); + summary.add(new SummaryRow(purchaseContext.getTitle().get(locale.getLanguage()), + formatCents(priceContainer.getSummarySrcPriceCts(), currencyCode), + priceBeforeVat, + subscriptionsToInclude.size(), + formatCents(priceContainer.getSummarySrcPriceCts() * subscriptionsToInclude.size(), currencyCode), + formatUnit(priceContainer.getNetPrice().multiply(new BigDecimal(subscriptionsToInclude.size())), currencyCode), + priceContainer.getSummarySrcPriceCts(), + SummaryRow.SummaryType.SUBSCRIPTION, + null, + reservationVatStatus)); + } + } else if(CollectionUtils.isNotEmpty(subscriptionsToInclude)) { + log.trace("subscriptions to include is not empty"); + var subscription = subscriptionsToInclude.get(0); + subscriptionRepository.findOne(subscription.getSubscriptionDescriptorId(), subscription.getOrganizationId()).ifPresent(subscriptionDescriptor -> { + log.trace("found subscriptionDescriptor with ID {}", subscriptionDescriptor.getId()); + // find tickets with subscription applied + var ticketsSubscription = tickets.stream().filter(t -> Objects.equals(subscription.getId(), t.getSubscriptionId())).collect(toList()); + final int ticketPriceCts = ticketsSubscription.stream().mapToInt(TicketPriceContainer::getSummarySrcPriceCts).sum(); + final int priceBeforeVat = SummaryPriceContainer.getSummaryPriceBeforeVatCts(ticketsSubscription); + summary.add(new SummaryRow(subscriptionDescriptor.getLocalizedTitle(locale), + "-" + formatCents(ticketPriceCts, currencyCode), + "-" + formatCents(priceBeforeVat, currencyCode), + ticketsSubscription.size(), + "-" + formatCents(ticketPriceCts, currencyCode), + "-" + formatCents(priceBeforeVat, currencyCode), + ticketPriceCts, + SummaryRow.SummaryType.APPLIED_SUBSCRIPTION, + null, + reservationVatStatus)); + }); + } + + // + return summary; + } + + public List<SummaryRow> extractSummary(String reservationId, PriceContainer.VatStatus reservationVatStatus, + PurchaseContext purchaseContext, Locale locale, PromoCodeDiscount promoCodeDiscount, TotalPrice reservationCost) { + List<Subscription> subscriptionsToInclude; + if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.event)) { + subscriptionsToInclude = subscriptionRepository.findAppliedSubscriptionByReservationId(reservationId) + .map(List::of) + .orElse(List.of()); + } else { + subscriptionsToInclude = subscriptionRepository.findSubscriptionsByReservationId(reservationId); + } + + return extractSummary(reservationVatStatus, + purchaseContext, + locale, + promoCodeDiscount, + reservationCost, + ticketRepository.findTicketsInReservation(reservationId), + reservationCostCalculator.streamAdditionalServiceItems(reservationId, purchaseContext), + subscriptionsToInclude); + } + + private String formatPromoCode(PromoCodeDiscount promoCodeDiscount, List<Ticket> tickets, Locale locale, PurchaseContext purchaseContext) { + + if(promoCodeDiscount.getCodeType() == PromoCodeDiscount.CodeType.DYNAMIC) { + return messageSourceManager.getMessageSourceFor(purchaseContext).getMessage("reservation.dynamic.discount.description", null, locale); //we don't expose the internal promo code + } + + List<Ticket> filteredTickets = tickets.stream().filter(ticket -> promoCodeDiscount.getCategories().contains(ticket.getCategoryId())).toList(); + + if (promoCodeDiscount.getCategories().isEmpty() || filteredTickets.isEmpty()) { + return promoCodeDiscount.getPromoCode(); + } + + String formattedDiscountedCategories = filteredTickets.stream() + .map(Ticket::getCategoryId) + .collect(toSet()) + .stream() + .map(categoryId -> ticketCategoryRepository.getByIdAndActive(categoryId, promoCodeDiscount.getEventId()).getName()) + .collect(Collectors.joining(", ", "(", ")")); + + + return promoCodeDiscount.getPromoCode() + " " + formattedDiscountedCategories; + } + + private static Function<Pair<AdditionalService, List<AdditionalServiceItem>>, Stream<? extends AdditionalServiceItemPriceContainer>> generateASIPriceContainers(PurchaseContext purchaseContext, PromoCodeDiscount discount) { + return p -> p.getValue().stream().map(asi -> AdditionalServiceItemPriceContainer.from(asi, p.getKey(), purchaseContext, discount)); + } +} diff --git a/src/main/java/alfio/manager/support/reservation/ReservationAuditingHelper.java b/src/main/java/alfio/manager/support/reservation/ReservationAuditingHelper.java new file mode 100644 index 0000000000..31519040fb --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/ReservationAuditingHelper.java @@ -0,0 +1,75 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +import alfio.model.Audit; +import alfio.model.Ticket; +import alfio.model.metadata.TicketMetadataContainer; +import alfio.repository.AuditingRepository; +import alfio.util.ObjectDiffUtil; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static alfio.model.Audit.EntityType.TICKET; + +public class ReservationAuditingHelper { + + private final AuditingRepository auditingRepository; + + public ReservationAuditingHelper(AuditingRepository auditingRepository) { + this.auditingRepository = auditingRepository; + } + + public void auditUpdateMetadata(String reservationId, + int ticketId, + int eventId, + TicketMetadataContainer newMetadata, + TicketMetadataContainer oldMetadata) { + List<Map<String, Object>> changes = ObjectDiffUtil.diff(oldMetadata, newMetadata, TicketMetadataContainer.class).stream() + .map(this::processChange) + .collect(Collectors.toList()); + + auditingRepository.insert(reservationId, null, eventId, Audit.EventType.UPDATE_TICKET_METADATA, new Date(), + TICKET, Integer.toString(ticketId), changes); + } + + public void auditUpdateTicket(Ticket preUpdateTicket, Map<String, String> preUpdateTicketFields, Ticket postUpdateTicket, Map<String, String> postUpdateTicketFields, int eventId) { + List<ObjectDiffUtil.Change> diffTicket = ObjectDiffUtil.diff(preUpdateTicket, postUpdateTicket); + List<ObjectDiffUtil.Change> diffTicketFields = ObjectDiffUtil.diff(preUpdateTicketFields, postUpdateTicketFields); + + List<Map<String, Object>> changes = Stream.concat(diffTicket.stream(), diffTicketFields.stream()) + .map(this::processChange) + .collect(Collectors.toList()); + + auditingRepository.insert(preUpdateTicket.getTicketsReservationId(), null, eventId, + Audit.EventType.UPDATE_TICKET, new Date(), TICKET, Integer.toString(preUpdateTicket.getId()), changes); + } + + private HashMap<String, Object> processChange(ObjectDiffUtil.Change change) { + var v = new HashMap<String, Object>(); + v.put("propertyName", change.getPropertyName()); + v.put("state", change.getState()); + v.put("oldValue", change.getOldValue()); + v.put("newValue", change.getNewValue()); + return v; + } +} diff --git a/src/main/java/alfio/manager/support/reservation/ReservationCostCalculator.java b/src/main/java/alfio/manager/support/reservation/ReservationCostCalculator.java new file mode 100644 index 0000000000..62b1b9f72f --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/ReservationCostCalculator.java @@ -0,0 +1,126 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +import alfio.manager.PurchaseContextManager; +import alfio.model.*; +import alfio.model.decorator.TicketPriceContainer; +import alfio.model.subscription.Subscription; +import alfio.repository.*; +import alfio.util.MonetaryUtil; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static alfio.util.MonetaryUtil.unitToCents; +import static java.util.stream.Collectors.toList; + +@Component +public class ReservationCostCalculator { + + private final TicketReservationRepository ticketReservationRepository; + private final PurchaseContextManager purchaseContextManager; + private final PromoCodeDiscountRepository promoCodeDiscountRepository; + private final SubscriptionRepository subscriptionRepository; + private final TicketRepository ticketRepository; + private final AdditionalServiceRepository additionalServiceRepository; + private final AdditionalServiceItemRepository additionalServiceItemRepository; + + public ReservationCostCalculator(TicketReservationRepository ticketReservationRepository, + PurchaseContextManager purchaseContextManager, + PromoCodeDiscountRepository promoCodeDiscountRepository, + SubscriptionRepository subscriptionRepository, + TicketRepository ticketRepository, + AdditionalServiceRepository additionalServiceRepository, + AdditionalServiceItemRepository additionalServiceItemRepository) { + this.ticketReservationRepository = ticketReservationRepository; + this.purchaseContextManager = purchaseContextManager; + this.promoCodeDiscountRepository = promoCodeDiscountRepository; + + this.subscriptionRepository = subscriptionRepository; + this.ticketRepository = ticketRepository; + this.additionalServiceRepository = additionalServiceRepository; + this.additionalServiceItemRepository = additionalServiceItemRepository; + } + + /** + * Get the total cost with VAT if it's not included in the ticket price. + * + * @param reservationId + * @return + */ + public Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(String reservationId) { + return totalReservationCostWithVAT(ticketReservationRepository.findReservationById(reservationId)); + } + + public Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(TicketReservation reservation) { + return totalReservationCostWithVAT(purchaseContextManager.findByReservationId(reservation.getId()).orElseThrow(), reservation, ticketRepository.findTicketsInReservation(reservation.getId())); + } + + private Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(PurchaseContext purchaseContext, TicketReservation reservation, List<Ticket> tickets) { + var promoCodeDiscount = Optional.ofNullable(reservation.getPromoCodeDiscountId()).map(promoCodeDiscountRepository::findById); + var subscriptions = subscriptionRepository.findSubscriptionsByReservationId(reservation.getId()); + var appliedSubscription = subscriptionRepository.findAppliedSubscriptionByReservationId(reservation.getId()); + return totalReservationCostWithVAT(promoCodeDiscount.orElse(null), purchaseContext, reservation, tickets, + purchaseContext.event().map(event -> collectAdditionalServiceItems(reservation.getId(), event)).orElse(List.of()), + subscriptions, + appliedSubscription); + } + + public static Pair<TotalPrice, Optional<PromoCodeDiscount>> totalReservationCostWithVAT(PromoCodeDiscount promoCodeDiscount, + PurchaseContext purchaseContext, + TicketReservation reservation, + List<Ticket> tickets, + List<Pair<AdditionalService, List<AdditionalServiceItem>>> additionalServiceItems, + List<Subscription> subscriptions, + Optional<Subscription> appliedSubscription) { + + String currencyCode = purchaseContext.getCurrency(); + List<TicketPriceContainer> ticketPrices = tickets.stream().map(t -> TicketPriceContainer.from(t, reservation.getVatStatus(), purchaseContext.getVat(), purchaseContext.getVatStatus(), promoCodeDiscount)).collect(toList()); + int discountedTickets = (int) ticketPrices.stream().filter(t -> t.getAppliedDiscount().compareTo(BigDecimal.ZERO) > 0).count(); + int discountAppliedCount = discountedTickets <= 1 || promoCodeDiscount.getDiscountType() == PromoCodeDiscount.DiscountType.FIXED_AMOUNT ? discountedTickets : 1; + if(discountAppliedCount == 0 && promoCodeDiscount != null && promoCodeDiscount.getDiscountType() == PromoCodeDiscount.DiscountType.FIXED_AMOUNT_RESERVATION) { + discountAppliedCount = 1; + } + var reservationPriceCalculator = alfio.manager.system.ReservationPriceCalculator.from(reservation, promoCodeDiscount, tickets, purchaseContext, additionalServiceItems, subscriptions, appliedSubscription); + var price = new TotalPrice(unitToCents(reservationPriceCalculator.getFinalPrice(), currencyCode), + unitToCents(reservationPriceCalculator.getVAT(), currencyCode), + -MonetaryUtil.unitToCents(reservationPriceCalculator.getAppliedDiscount(), currencyCode), + discountAppliedCount, + currencyCode); + return Pair.of(price, Optional.ofNullable(promoCodeDiscount)); + } + + Stream<Pair<AdditionalService, List<AdditionalServiceItem>>> streamAdditionalServiceItems(String reservationId, PurchaseContext purchaseContext) { + return purchaseContext.event().map(event -> { + return additionalServiceItemRepository.findByReservationUuid(reservationId) + .stream() + .collect(Collectors.groupingBy(AdditionalServiceItem::getAdditionalServiceId)) + .entrySet() + .stream() + .map(entry -> Pair.of(additionalServiceRepository.getById(entry.getKey(), event.getId()), entry.getValue())); + }).orElse(Stream.empty()); + } + public List<Pair<AdditionalService, List<AdditionalServiceItem>>> collectAdditionalServiceItems(String reservationId, Event event) { + return streamAdditionalServiceItems(reservationId, event).collect(Collectors.toList()); + } +} diff --git a/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java b/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java new file mode 100644 index 0000000000..860e2b89b3 --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java @@ -0,0 +1,341 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +import alfio.controller.support.TemplateProcessor; +import alfio.manager.BillingDocumentManager; +import alfio.manager.ExtensionManager; +import alfio.manager.NotificationManager; +import alfio.manager.i18n.MessageSourceManager; +import alfio.manager.support.ConfirmationEmailConfiguration; +import alfio.manager.support.IncompatibleStateException; +import alfio.manager.support.PartialTicketTextGenerator; +import alfio.manager.system.ConfigurationLevel; +import alfio.manager.system.ConfigurationManager; +import alfio.manager.system.Mailer; +import alfio.model.*; +import alfio.model.extension.CustomEmailText; +import alfio.model.metadata.SubscriptionMetadata; +import alfio.model.subscription.Subscription; +import alfio.model.subscription.UsageDetails; +import alfio.model.system.ConfigurationKeys; +import alfio.model.user.Organization; +import alfio.repository.*; +import alfio.repository.user.OrganizationRepository; +import alfio.util.*; +import alfio.util.checkin.TicketCheckInUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.*; + +import static alfio.model.BillingDocument.Type.INVOICE; +import static alfio.model.BillingDocument.Type.RECEIPT; +import static alfio.model.system.ConfigurationKeys.*; +import static alfio.util.ReservationUtil.reservationUrl; +import static java.util.stream.Collectors.*; +import static org.springframework.http.MediaType.APPLICATION_PDF; + +@Component +public class ReservationEmailContentHelper { + + private static final String RESERVATION_ID = "reservationId"; + private final ConfigurationManager configurationManager; + private final NotificationManager notificationManager; + private final SubscriptionRepository subscriptionRepository; + private final MessageSourceManager messageSourceManager; + private final OrderSummaryGenerator orderSummaryGenerator; + private final TicketReservationRepository ticketReservationRepository; + private final TicketCategoryRepository ticketCategoryRepository; + private final TicketFieldRepository ticketFieldRepository; + private final OrganizationRepository organizationRepository; + private final TicketRepository ticketRepository; + private final TemplateManager templateManager; + private final BillingDocumentManager billingDocumentManager; + private final ExtensionManager extensionManager; + private final EventRepository eventRepository; + + + public ReservationEmailContentHelper(ConfigurationManager configurationManager, + NotificationManager notificationManager, + SubscriptionRepository subscriptionRepository, + MessageSourceManager messageSourceManager, + OrderSummaryGenerator orderSummaryGenerator, + TicketReservationRepository ticketReservationRepository, + TicketCategoryRepository ticketCategoryRepository, + TicketFieldRepository ticketFieldRepository, + OrganizationRepository organizationRepository, + TicketRepository ticketRepository, + TemplateManager templateManager, + BillingDocumentManager billingDocumentManager, + ExtensionManager extensionManager, + EventRepository eventRepository) { + this.configurationManager = configurationManager; + this.notificationManager = notificationManager; + this.subscriptionRepository = subscriptionRepository; + this.messageSourceManager = messageSourceManager; + this.orderSummaryGenerator = orderSummaryGenerator; + this.ticketReservationRepository = ticketReservationRepository; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketFieldRepository = ticketFieldRepository; + this.organizationRepository = organizationRepository; + this.ticketRepository = ticketRepository; + this.templateManager = templateManager; + this.billingDocumentManager = billingDocumentManager; + this.extensionManager = extensionManager; + this.eventRepository = eventRepository; + } + + + public void sendConfirmationEmail(PurchaseContext purchaseContext, TicketReservation ticketReservation, Locale language, String username) { + String reservationId = ticketReservation.getId(); + checkIfFinalized(reservationId); + OrderSummary summary = orderSummaryGenerator.orderSummaryForReservationId(reservationId, purchaseContext); + + List<Mailer.Attachment> attachments; + if (configurationManager.canAttachBillingDocumentToConfirmationEmail(purchaseContext)) { // https://github.com/alfio-event/alf.io/issues/573 + attachments = generateAttachmentForConfirmationEmail(purchaseContext, ticketReservation, language, summary, username); + } else{ + attachments = List.of(); + } + var vat = getVAT(purchaseContext); + + List<ConfirmationEmailConfiguration> configurations = new ArrayList<>(); + if(purchaseContext.ofType(PurchaseContext.PurchaseContextType.subscription)) { + var firstSubscription = subscriptionRepository.findSubscriptionsByReservationId(reservationId).stream().findFirst().orElseThrow(); + boolean sendSeparateEmailToOwner = !Objects.equals(firstSubscription.getEmail(), ticketReservation.getEmail()); + var metadata = Objects.requireNonNullElseGet(subscriptionRepository.getSubscriptionMetadata(firstSubscription.getId()), SubscriptionMetadata::empty); + Map<String, Object> initialModel = Map.of( + "pin", firstSubscription.getPin(), + "subscriptionId", firstSubscription.getId(), + "includePin", metadata.getConfiguration().isDisplayPin(), + "fullName", firstSubscription.getFirstName() + " " + firstSubscription.getLastName()); + var model = prepareModelForReservationEmail(purchaseContext, ticketReservation, vat, summary, List.of(), initialModel); + var subscriptionAttachments = new ArrayList<>(attachments); + subscriptionAttachments.add(generateSubscriptionAttachment(firstSubscription)); + configurations.add(new ConfirmationEmailConfiguration(TemplateResource.CONFIRMATION_EMAIL_SUBSCRIPTION, firstSubscription.getEmail(), model, sendSeparateEmailToOwner ? List.of() : subscriptionAttachments)); + if(sendSeparateEmailToOwner) { + var separateModel = new HashMap<>(model); + separateModel.put("includePin", false); + separateModel.put("fullName", ticketReservation.getFullName()); + configurations.add(new ConfirmationEmailConfiguration(TemplateResource.CONFIRMATION_EMAIL_SUBSCRIPTION, ticketReservation.getEmail(), separateModel, subscriptionAttachments)); + } + } else { + var model = prepareModelForReservationEmail(purchaseContext, ticketReservation, vat, summary, ticketRepository.findTicketsInReservation(ticketReservation.getId()), Map.of()); + configurations.add(new ConfirmationEmailConfiguration(TemplateResource.CONFIRMATION_EMAIL, ticketReservation.getEmail(), model, attachments)); + } + + var messageSource = messageSourceManager.getMessageSourceFor(purchaseContext); + var localizedType = messageSource.getMessage("purchase-context."+purchaseContext.getType(), null, language); + configurations.forEach(configuration -> { + notificationManager.sendSimpleEmail(purchaseContext, ticketReservation.getId(), configuration.getEmailAddress(), messageSource.getMessage("reservation-email-subject", + new Object[]{ configurationManager.getShortReservationID(purchaseContext, ticketReservation), purchaseContext.getTitle().get(language.getLanguage()), localizedType}, language), + () -> templateManager.renderTemplate(purchaseContext, configuration.getTemplateResource(), configuration.getModel(), language), + configuration.getAttachments()); + }); + } + + private Mailer.Attachment generateSubscriptionAttachment(Subscription subscription) { + var model = new HashMap<String, String>(); + model.put("subscriptionId", subscription.getId().toString()); + return new Mailer.Attachment("subscription_" + subscription.getId() + ".pdf", null, APPLICATION_PDF.toString(), model, Mailer.AttachmentIdentifier.SUBSCRIPTION_PDF); + } + + private List<Mailer.Attachment> generateAttachmentForConfirmationEmail(PurchaseContext purchaseContext, + TicketReservation ticketReservation, + Locale language, + OrderSummary summary, + String username) { + if(mustGenerateBillingDocument(summary, ticketReservation)) { //#459 - include PDF invoice in reservation email + BillingDocument.Type type = ticketReservation.getHasInvoiceNumber() ? INVOICE : RECEIPT; + return billingDocumentManager.generateBillingDocumentAttachment(purchaseContext, ticketReservation, language, type, username, summary); + } + return List.of(); + } + + public void sendReservationCompleteEmailToOrganizer(PurchaseContext purchaseContext, TicketReservation ticketReservation, Locale language, String username) { + String reservationId = ticketReservation.getId(); + + checkIfFinalized(reservationId); + + Organization organization = organizationRepository.getById(purchaseContext.getOrganizationId()); + List<String> cc = notificationManager.getCCForEventOrganizer(purchaseContext); + + Map<String, Object> reservationEmailModel = prepareModelForReservationEmail(purchaseContext, ticketReservation); + + OrderSummary summary = orderSummaryGenerator.orderSummaryForReservationId(reservationId, purchaseContext); + + List<Mailer.Attachment> attachments = Collections.emptyList(); + + if (!configurationManager.canGenerateReceiptOrInvoiceToCustomer(purchaseContext) || configurationManager.isInvoiceOnly(purchaseContext)) { // https://github.com/alfio-event/alf.io/issues/573 + attachments = generateAttachmentForConfirmationEmail(purchaseContext, ticketReservation, language, summary, username); + } + + + String shortReservationID = configurationManager.getShortReservationID(purchaseContext, ticketReservation); + notificationManager.sendSimpleEmail(purchaseContext, null, organization.getEmail(), cc, "Reservation complete " + shortReservationID, + () -> templateManager.renderTemplate(purchaseContext, TemplateResource.CONFIRMATION_EMAIL_FOR_ORGANIZER, reservationEmailModel, language), + attachments); + } + + private static boolean mustGenerateBillingDocument(OrderSummary summary, TicketReservation ticketReservation) { + return !summary.getFree() && (!summary.getNotYetPaid() || (summary.getWaitingForPayment() && ticketReservation.isInvoiceRequested())); + } + + public List<Mailer.Attachment> generateBillingDocumentAttachment(PurchaseContext purchaseContext, + TicketReservation ticketReservation, + Locale language, + Map<String, Object> billingDocumentModel, + BillingDocument.Type documentType) { + Map<String, String> model = new HashMap<>(); + model.put(RESERVATION_ID, ticketReservation.getId()); + purchaseContext.event().ifPresent(event -> model.put("eventId", Integer.toString(event.getId()))); + model.put("language", Json.toJson(language)); + model.put("reservationEmailModel", Json.toJson(billingDocumentModel));//ticketReservation.getHasInvoiceNumber() + switch (documentType) { + case INVOICE: + return Collections.singletonList(new Mailer.Attachment("invoice.pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.INVOICE_PDF)); + case RECEIPT: + return Collections.singletonList(new Mailer.Attachment("receipt.pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.RECEIPT_PDF)); + case CREDIT_NOTE: + return Collections.singletonList(new Mailer.Attachment("credit-note.pdf", null, "application/pdf", model, Mailer.AttachmentIdentifier.CREDIT_NOTE_PDF)); + default: + throw new IllegalStateException(documentType+" is not supported"); + } + } + + public String getReservationEmailSubject(PurchaseContext purchaseContext, Locale reservationLanguage, String key, String id) { + return messageSourceManager.getMessageSourceFor(purchaseContext) + .getMessage(key, new Object[]{id, purchaseContext.getDisplayName()}, reservationLanguage); + } + + public Map<String, Object> prepareModelForReservationEmail(PurchaseContext purchaseContext, + TicketReservation reservation, + Optional<String> vat, + OrderSummary summary, + List<Ticket> ticketsToInclude, + Map<String, Object> initialOptions) { + Organization organization = organizationRepository.getById(purchaseContext.getOrganizationId()); + String baseUrl = configurationManager.baseUrl(purchaseContext); + var reservationId = reservation.getId(); + String reservationUrl = reservationUrl(reservation, purchaseContext, configurationManager); + String reservationShortID = configurationManager.getShortReservationID(purchaseContext, reservation); + + var bankingInfo = configurationManager.getFor(Set.of(INVOICE_ADDRESS, BANK_ACCOUNT_NR, BANK_ACCOUNT_OWNER), ConfigurationLevel.purchaseContext(purchaseContext)); + Optional<String> invoiceAddress = bankingInfo.get(INVOICE_ADDRESS).getValue(); + Optional<String> bankAccountNr = bankingInfo.get(BANK_ACCOUNT_NR).getValue(); + Optional<String> bankAccountOwner = bankingInfo.get(BANK_ACCOUNT_OWNER).getValue(); + + Map<Integer, List<Ticket>> ticketsByCategory = ticketsToInclude + .stream() + .collect(groupingBy(Ticket::getCategoryId)); + final List<TicketWithCategory> ticketsWithCategory = ReservationUtil.collectTicketsWithCategory(ticketsByCategory, ticketCategoryRepository); + Map<String, Object> baseModel = new HashMap<>(); + baseModel.putAll(initialOptions); + baseModel.putAll(extensionManager.handleReservationEmailCustomText(purchaseContext, reservation, ticketReservationRepository.getAdditionalInfo(reservationId)) + .map(CustomEmailText::toMap) + .orElse(Map.of())); + Map<String, Object> model = TemplateResource.prepareModelForConfirmationEmail(organization, purchaseContext, reservation, vat, ticketsWithCategory, summary, baseUrl, reservationUrl, reservationShortID, invoiceAddress, bankAccountNr, bankAccountOwner, baseModel); + boolean euBusiness = StringUtils.isNotBlank(reservation.getVatCountryCode()) && StringUtils.isNotBlank(reservation.getVatNr()) + && configurationManager.getForSystem(ConfigurationKeys.EU_COUNTRIES_LIST).getRequiredValue().contains(reservation.getVatCountryCode()) + && PriceContainer.VatStatus.isVatExempt(reservation.getVatStatus()); + model.put("euBusiness", euBusiness); + model.put("publicId", configurationManager.getPublicReservationID(purchaseContext, reservation)); + model.put("invoicingAdditionalInfo", ticketReservationRepository.getAdditionalInfo(reservationId).getInvoicingAdditionalInfo()); + if(purchaseContext.getType() == PurchaseContext.PurchaseContextType.event) { + var event = purchaseContext.event().orElseThrow(); + model.put("displayLocation", ticketsWithCategory.stream() + .noneMatch(tc -> EventUtil.isAccessOnline(tc.getCategory(), event))); + } else { + model.put("displayLocation", false); + } + if(ticketReservationRepository.hasSubscriptionApplied(reservationId)) { + model.put("displaySubscriptionUsage", true); + var subscription = subscriptionRepository.findAppliedSubscriptionByReservationId(reservationId).orElseThrow(); + if(subscription.getMaxEntries() > -1) { + var subscriptionUsageDetails = UsageDetails.fromSubscription(subscription, ticketRepository.countSubscriptionUsage(subscription.getId(), null)); + model.put("subscriptionUsageDetails", subscriptionUsageDetails); + model.put("subscriptionUrl", reservationUrl(reservation, purchaseContext, configurationManager)); + } + } + return model; + } + + public void sendTicketByEmail(Ticket ticket, Locale locale, Event event, PartialTicketTextGenerator confirmationTextBuilder) { + TicketReservation reservation = ticketReservationRepository.findReservationById(ticket.getTicketsReservationId()); + checkIfFinalized(reservation.getId()); + TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()); + notificationManager.sendTicketByEmail(ticket, event, locale, confirmationTextBuilder, reservation, ticketCategory, () -> retrieveAttendeeAdditionalInfoForTicket(ticket)); + } + + private void checkIfFinalized(String reservationId) { + if (!Boolean.TRUE.equals(ticketReservationRepository.checkIfFinalized(reservationId))) { + throw new IncompatibleStateException("Reservation was confirmed but not finalized yet. Cannot send emails."); + } + } + + public Map<String, List<String>> retrieveAttendeeAdditionalInfoForTicket(Ticket ticket) { + return ticketFieldRepository.findNameAndValue(ticket.getId()) + .stream() + .collect(groupingBy(FieldNameAndValue::getName, mapping(FieldNameAndValue::getValue, toList()))); + } + + public PartialTicketTextGenerator getTicketEmailGenerator(Event event, + TicketReservation ticketReservation, + Locale ticketLanguage, + Map<String, List<String>> additionalInfo) { + return ticket -> { + Organization organization = organizationRepository.getById(event.getOrganizationId()); + String ticketUrl = ReservationUtil.ticketUpdateUrl(event, ticket, configurationManager); + var ticketCategory = ticketCategoryRepository.getById(ticket.getCategoryId()); + + var initialModel = new HashMap<>(extensionManager.handleTicketEmailCustomText(event, ticketReservation, ticketReservationRepository.getAdditionalInfo(ticketReservation.getId()), ticketFieldRepository.findAllByTicketId(ticket.getId())) + .map(CustomEmailText::toMap) + .orElse(Map.of())); + if(EventUtil.isAccessOnline(ticketCategory, event)) { + initialModel.putAll(TicketCheckInUtil.getOnlineCheckInInfo( + extensionManager, + eventRepository, + ticketCategoryRepository, + configurationManager, + event, + ticketLanguage, + ticket, + ticketCategory, + additionalInfo + )); + } + var baseUrl = StringUtils.removeEnd(configurationManager.getFor(BASE_URL, ConfigurationLevel.event(event)).getRequiredValue(), "/"); + var calendarUrl = UriComponentsBuilder.fromUriString(baseUrl + "/api/v2/public/event/{eventShortName}/calendar/{currentLang}") + .queryParam("type", "google") + .build(Map.of("eventShortName", event.getShortName(), "currentLang", ticketLanguage.getLanguage())) + .toString(); + return TemplateProcessor.buildPartialEmail(event, organization, ticketReservation, ticketCategory, templateManager, baseUrl, ticketUrl, calendarUrl, ticketLanguage, initialModel).generate(ticket); + }; + } + + public Optional<String> getVAT(PurchaseContext purchaseContext) { + return configurationManager.getFor(VAT_NR, purchaseContext.getConfigurationLevel()).getValue(); + } + + public Map<String, Object> prepareModelForReservationEmail(PurchaseContext purchaseContext, TicketReservation reservation) { + Optional<String> vat = getVAT(purchaseContext); + OrderSummary summary = orderSummaryGenerator.orderSummaryForReservationId(reservation.getId(), purchaseContext); + return prepareModelForReservationEmail(purchaseContext, reservation, vat, summary, ticketRepository.findTicketsInReservation(reservation.getId()), Map.of()); + } +} diff --git a/src/main/java/alfio/manager/support/reservation/TooManyTicketsForDiscountCodeException.java b/src/main/java/alfio/manager/support/reservation/TooManyTicketsForDiscountCodeException.java new file mode 100644 index 0000000000..8c84a7f297 --- /dev/null +++ b/src/main/java/alfio/manager/support/reservation/TooManyTicketsForDiscountCodeException.java @@ -0,0 +1,20 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +public class TooManyTicketsForDiscountCodeException extends RuntimeException { +} diff --git a/src/main/java/alfio/manager/support/response/ValidatedResponse.java b/src/main/java/alfio/manager/support/response/ValidatedResponse.java index 39a527b72d..306520fa9e 100644 --- a/src/main/java/alfio/manager/support/response/ValidatedResponse.java +++ b/src/main/java/alfio/manager/support/response/ValidatedResponse.java @@ -20,34 +20,34 @@ import alfio.model.result.Result; import alfio.model.result.ValidationResult; import alfio.model.result.WarningMessage; -import lombok.AllArgsConstructor; -import lombok.Getter; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -@AllArgsConstructor public class ValidatedResponse<T> { private final ValidationResult validationResult; private final T value; + public ValidatedResponse(ValidationResult validationResult, T value) { + this.validationResult = validationResult; + this.value = value; + } + public static <T> ValidatedResponse<T> toResponse(BindingResult bindingResult, T value) { var transformed = bindingResult.getAllErrors().stream().map(objectError -> { - if (objectError instanceof FieldError) { - var fe = (FieldError) objectError; + if (objectError instanceof FieldError fe) { return new ValidationResult.ErrorDescriptor(fe.getField(), "", fe.getCode(), fe.getArguments()); } else { return new ValidationResult.ErrorDescriptor(objectError.getObjectName(), "", objectError.getCode(), objectError.getArguments()); } - }).collect(Collectors.toList()); + }).toList(); - List<WarningMessage> warnings = bindingResult instanceof CustomBindingResult ? ((CustomBindingResult)bindingResult).getWarnings() : List.of(); + List<WarningMessage> warnings = bindingResult instanceof CustomBindingResult cbd ? cbd.getWarnings() : List.of(); return new ValidatedResponse<>(ValidationResult.failed(transformed, warnings), value); } @@ -57,7 +57,7 @@ public static <T> ValidatedResponse<T> fromResult(Result<T> result, String objec } var transformed = result.getErrors().stream() .map(ec -> new ValidationResult.ErrorDescriptor(objectName, "", ec.getCode())) - .collect(Collectors.toList()); + .toList(); return new ValidatedResponse<>(ValidationResult.failed(transformed), null); } @@ -73,7 +73,7 @@ public boolean isSuccess() { public List<ErrorDescriptor> getValidationErrors() { return validationResult.getValidationErrors().stream() .map(ed -> new ErrorDescriptor(ed.getFieldName(), ed.getCode(), fromArray(ed.getArguments()))) - .collect(Collectors.toList()); + .toList(); } public int getErrorCount() { @@ -101,11 +101,28 @@ private static Map<String, Object> fromArray(Object[] arguments) { } } - @AllArgsConstructor - @Getter + public static class ErrorDescriptor { private final String fieldName; private final String code; private final Map<String, Object> arguments; + + public String getFieldName() { + return fieldName; + } + + public String getCode() { + return code; + } + + public Map<String, Object> getArguments() { + return arguments; + } + + public ErrorDescriptor(String fieldName, String code, Map<String, Object> arguments) { + this.fieldName = fieldName; + this.code = code; + this.arguments = arguments; + } } } diff --git a/src/main/java/alfio/manager/system/AdminJobExecutor.java b/src/main/java/alfio/manager/system/AdminJobExecutor.java index f4c1364690..250fee3691 100644 --- a/src/main/java/alfio/manager/system/AdminJobExecutor.java +++ b/src/main/java/alfio/manager/system/AdminJobExecutor.java @@ -24,14 +24,25 @@ public interface AdminJobExecutor { enum JobName { - CHECK_OFFLINE_PAYMENTS, - SEND_TICKET_ASSIGNMENT_REMINDER, - SEND_OFFLINE_PAYMENT_REMINDER, - UNKNOWN, - SEND_OFFLINE_PAYMENT_TO_ORGANIZER, - REGENERATE_INVOICES, - ASSIGN_TICKETS_TO_SUBSCRIBERS, - EXECUTE_EXTENSION; + CHECK_OFFLINE_PAYMENTS(false), + SEND_TICKET_ASSIGNMENT_REMINDER(false), + SEND_OFFLINE_PAYMENT_REMINDER(false), + UNKNOWN(false), + SEND_OFFLINE_PAYMENT_TO_ORGANIZER(false), + REGENERATE_INVOICES(false), + ASSIGN_TICKETS_TO_SUBSCRIBERS(false), + EXECUTE_EXTENSION(true), + RETRY_RESERVATION_CONFIRMATION(true); + + private final boolean allowsMultiple; + + JobName(boolean allowsMultiple) { + this.allowsMultiple = allowsMultiple; + } + + public boolean allowsMultipleScheduling() { + return allowsMultiple; + } public static JobName safeValueOf(String value) { return Arrays.stream(values()) diff --git a/src/main/java/alfio/manager/system/AdminJobManager.java b/src/main/java/alfio/manager/system/AdminJobManager.java index 94527157a0..d9595d7d1e 100644 --- a/src/main/java/alfio/manager/system/AdminJobManager.java +++ b/src/main/java/alfio/manager/system/AdminJobManager.java @@ -22,10 +22,10 @@ import alfio.model.system.AdminJobSchedule; import alfio.repository.system.AdminJobQueueRepository; import alfio.util.ClockProvider; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Transactional; @@ -42,15 +42,17 @@ import static java.util.stream.Collectors.*; @Transactional -@Slf4j public class AdminJobManager { + private static final Logger log = LoggerFactory.getLogger(AdminJobManager.class); + static final int MAX_ATTEMPTS = 17; // will retry for approximately 36h - private static final Set<String> ADMIN_JOBS = EnumSet.complementOf(EnumSet.of(JobName.EXECUTE_EXTENSION)) - .stream() + private static final Set<JobName> REGULAR = EnumSet.complementOf(EnumSet.of(JobName.EXECUTE_EXTENSION, JobName.RETRY_RESERVATION_CONFIRMATION)); + private static final Set<String> ADMIN_JOBS = REGULAR.stream() .map(Enum::name) .collect(toSet()); private static final Set<String> EXTENSIONS_JOB = Set.of(JobName.EXECUTE_EXTENSION.name()); + private static final Set<String> RESERVATIONS_JOB = Set.of(JobName.RETRY_RESERVATION_CONFIRMATION.name()); private final Map<JobName, List<AdminJobExecutor>> executorsByJobId; private final AdminJobQueueRepository adminJobQueueRepository; private final TransactionTemplate nestedTransactionTemplate; @@ -74,19 +76,15 @@ public AdminJobManager(List<AdminJobExecutor> jobExecutors, this.clockProvider = clockProvider; } - @Scheduled(fixedDelay = 1000L) - void processPendingExtensionRetry() { - log.trace("Processing pending extensions retry"); - processPendingExtensionRetry(ZonedDateTime.now(clockProvider.getClock())); - log.trace("done processing pending extensions retry"); - } - // internal method invoked by tests void processPendingExtensionRetry(ZonedDateTime timestamp) { internalProcessPendingSchedules(adminJobQueueRepository.loadPendingSchedules(EXTENSIONS_JOB, timestamp)); } - @Scheduled(fixedDelay = 60 * 1000) + void processPendingReservationsRetry(ZonedDateTime timestamp) { + internalProcessPendingSchedules(adminJobQueueRepository.loadPendingSchedules(RESERVATIONS_JOB, timestamp)); + } + void processPendingRequests() { log.trace("Processing pending requests"); internalProcessPendingSchedules(adminJobQueueRepository.loadPendingSchedules(ADMIN_JOBS, ZonedDateTime.now(clockProvider.getClock()))); @@ -102,12 +100,11 @@ private void internalProcessPendingSchedules(List<AdminJobSchedule> pendingSched var partitionedResults = scheduleWithResults.getRight().stream().collect(Collectors.partitioningBy(Result::isSuccess)); if(!partitionedResults.get(false).isEmpty()) { partitionedResults.get(false).forEach(r -> log.warn("Processing failed for {}: {}", schedule.getJobName(), r.getErrors())); - if (schedule.getJobName() != JobName.EXECUTE_EXTENSION || schedule.getAttempts() > MAX_ATTEMPTS) { + if (REGULAR.contains(schedule.getJobName()) || schedule.getAttempts() > MAX_ATTEMPTS) { adminJobQueueRepository.updateSchedule(schedule.getId(), AdminJobSchedule.Status.FAILED, ZonedDateTime.now(clockProvider.getClock()), Map.of()); } else { var nextExecution = getNextExecution(schedule.getAttempts()); - var extensionName = schedule.getMetadata().get("extensionName"); - log.debug("scheduling failed extension {} to be executed at {}", extensionName, nextExecution); + logReschedule(nextExecution, schedule.getMetadata(), schedule.getJobName()); adminJobQueueRepository.scheduleRetry(schedule.getId(), nextExecution); } } else { @@ -126,7 +123,6 @@ static ZonedDateTime getNextExecution(int currentAttempt) { .plusSeconds((long) Math.pow(2, currentAttempt + 1D)); } - @Scheduled(cron = "#{environment.acceptsProfiles('dev') ? '0 * * * * *' : '0 0 0 * * *'}") void cleanupExpiredRequests() { log.trace("Cleanup expired requests"); ZonedDateTime now = ZonedDateTime.now(clockProvider.getClock()); @@ -164,7 +160,10 @@ private Pair<AdminJobSchedule, List<Result<String>>> processPendingRequest(Admin public static Function<AdminJobQueueRepository, Boolean> executionScheduler(JobName jobName, Map<String, Object> metadata, ZonedDateTime executionTime) { return adminJobQueueRepository -> { try { - int result = adminJobQueueRepository.schedule(jobName, executionTime, metadata); + int result = adminJobQueueRepository.schedule(jobName, executionTime, metadata, + // by setting a null value, we actually disable the unique constraint for this job name + // and allow multiple rows to be present for the same timestamp + jobName.allowsMultipleScheduling() ? null : "N"); if (result == 0) { log.trace("Possible duplication detected while inserting {}", jobName); } @@ -175,4 +174,15 @@ public static Function<AdminJobQueueRepository, Boolean> executionScheduler(JobN } }; } + + private static void logReschedule(ZonedDateTime nextExecution, Map<String, Object> metadata, JobName jobName) { + String name; + boolean isExtension = jobName == JobName.EXECUTE_EXTENSION; + if (isExtension) { + name = String.valueOf(metadata.get("extensionName")); + } else { + name = String.valueOf(metadata.get("reservationId")); + } + log.debug("scheduling failed {} {} to be executed at {}", isExtension ? "extension" : "reservation", name, nextExecution); + } } diff --git a/src/main/java/alfio/manager/system/AdminJobManagerScheduler.java b/src/main/java/alfio/manager/system/AdminJobManagerScheduler.java new file mode 100644 index 0000000000..0612bc7c5a --- /dev/null +++ b/src/main/java/alfio/manager/system/AdminJobManagerScheduler.java @@ -0,0 +1,68 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.system; + +import alfio.config.Initializer; +import alfio.util.ClockProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.ZonedDateTime; + +@Component +@Profile("!" + Initializer.PROFILE_DISABLE_JOBS) +public class AdminJobManagerScheduler { + + private static final Logger log = LoggerFactory.getLogger(AdminJobManagerScheduler.class); + private final AdminJobManager adminJobManager; + private final ClockProvider clockProvider; + + public AdminJobManagerScheduler(AdminJobManager adminJobManager, + ClockProvider clockProvider) { + this.adminJobManager = adminJobManager; + this.clockProvider = clockProvider; + } + + @Scheduled(fixedDelay = 1000L) + void processPendingExtensionRetry() { + log.trace("Processing pending extensions retry"); + adminJobManager.processPendingExtensionRetry(ZonedDateTime.now(clockProvider.getClock())); + log.trace("done processing pending extensions retry"); + } + + @Scheduled(fixedDelay = 1000L) + void processPendingReservationsRetry() { + log.trace("Processing pending reservations retry"); + adminJobManager.processPendingReservationsRetry(ZonedDateTime.now(clockProvider.getClock())); + log.trace("done processing pending reservations retry"); + } + + @Scheduled(fixedDelay = 60 * 1000) + void processPendingRequests() { + log.trace("Processing pending requests"); + adminJobManager.processPendingRequests(); + log.trace("done processing pending requests"); + } + + @Scheduled(cron = "#{environment.acceptsProfiles('dev') ? '0 * * * * *' : '0 0 0 * * *'}") + void cleanupExpiredRequests() { + adminJobManager.cleanupExpiredRequests(); + } +} diff --git a/src/main/java/alfio/manager/system/BaseMailer.java b/src/main/java/alfio/manager/system/BaseMailer.java new file mode 100644 index 0000000000..a3da8b34a8 --- /dev/null +++ b/src/main/java/alfio/manager/system/BaseMailer.java @@ -0,0 +1,64 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.system; + +import alfio.manager.system.ConfigurationManager.MaybeConfiguration; +import alfio.model.system.ConfigurationKeys; +import alfio.repository.user.OrganizationRepository; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.commons.lang3.StringUtils; + +import java.time.Duration; +import java.util.Map; +import java.util.function.Consumer; + +import static java.util.Objects.requireNonNull; + +abstract class BaseMailer implements Mailer { + + static final String MISSING_CONFIG_MESSAGE = "config cannot be null"; + private final OrganizationRepository organizationRepository; + private static final Cache<Integer, String> ORG_ADDRESS_CACHE = Caffeine.newBuilder() + .expireAfterWrite(Duration.ofMinutes(1L)) + .build(); + + BaseMailer(OrganizationRepository organizationRepository) { + this.organizationRepository = organizationRepository; + } + + void setReplyToIfPresent(Map<ConfigurationKeys, MaybeConfiguration> conf, + int organizationId, + Consumer<String> replyToSetter) { + var replyToConfig = requireNonNull(requireNonNull(conf, MISSING_CONFIG_MESSAGE).get(ConfigurationKeys.MAIL_REPLY_TO), "MAIL_REPLY_TO is required") + .getValueOrDefault(""); + if (StringUtils.isNotBlank(replyToConfig)) { + replyToSetter.accept(replyToConfig); + } else if(requireNonNull(conf.get(ConfigurationKeys.MAIL_SET_ORG_REPLY_TO), "MAIL_SET_ORG_REPLY_TO is required").getValueAsBooleanOrDefault()) { + var address = ORG_ADDRESS_CACHE.get(organizationId, id -> { + var organization = organizationRepository.getById(organizationId); + if (StringUtils.isNotBlank(organization.getEmail())) { + return organization.getEmail(); + } + return null; + }); + if (StringUtils.isNotBlank(address)) { + replyToSetter.accept(address); + } + } + } +} diff --git a/src/main/java/alfio/manager/system/ConfigurationLevels.java b/src/main/java/alfio/manager/system/ConfigurationLevels.java index e47450d0b5..49c496a4e3 100644 --- a/src/main/java/alfio/manager/system/ConfigurationLevels.java +++ b/src/main/java/alfio/manager/system/ConfigurationLevels.java @@ -17,7 +17,6 @@ package alfio.manager.system; import alfio.model.system.ConfigurationPathLevel; -import lombok.AllArgsConstructor; import java.util.OptionalInt; @@ -39,10 +38,13 @@ public ConfigurationPathLevel getPathLevel() { } } - @AllArgsConstructor static class OrganizationLevel implements ConfigurationLevel { final int organizationId; + OrganizationLevel(int organizationId) { + this.organizationId = organizationId; + } + @Override public ConfigurationPathLevel getPathLevel() { return ORGANIZATION; @@ -54,11 +56,15 @@ public OptionalInt getOrganizationId() { } } - @AllArgsConstructor static class EventLevel implements ConfigurationLevel { final int organizationId; final int eventId; + EventLevel(int organizationId, int eventId) { + this.organizationId = organizationId; + this.eventId = eventId; + } + @Override public ConfigurationPathLevel getPathLevel() { return EVENT; @@ -75,12 +81,18 @@ public OptionalInt getEventId() { } } - @AllArgsConstructor + static class CategoryLevel implements ConfigurationLevel { final int organizationId; final int eventId; final int categoryId; + CategoryLevel(int organizationId, int eventId, int categoryId) { + this.organizationId = organizationId; + this.eventId = eventId; + this.categoryId = categoryId; + } + @Override public ConfigurationPathLevel getPathLevel() { return TICKET_CATEGORY; diff --git a/src/main/java/alfio/manager/system/ConfigurationManager.java b/src/main/java/alfio/manager/system/ConfigurationManager.java index f9574a2384..c0eecbd45f 100644 --- a/src/main/java/alfio/manager/system/ConfigurationManager.java +++ b/src/main/java/alfio/manager/system/ConfigurationManager.java @@ -19,15 +19,13 @@ import alfio.config.Initializer; import alfio.controller.api.v2.model.AlfioInfo; import alfio.controller.api.v2.model.AnalyticsConfiguration; +import alfio.controller.api.v2.model.WalletConfiguration; import alfio.controller.api.v2.user.support.PurchaseContextInfoBuilder; import alfio.manager.system.ConfigurationLevels.CategoryLevel; import alfio.manager.system.ConfigurationLevels.EventLevel; import alfio.manager.system.ConfigurationLevels.OrganizationLevel; import alfio.manager.user.UserManager; -import alfio.model.Configurable; -import alfio.model.EventAndOrganizationId; -import alfio.model.PurchaseContext; -import alfio.model.TicketReservation; +import alfio.model.*; import alfio.model.modification.ConfigurationModification; import alfio.model.system.Configuration; import alfio.model.system.Configuration.*; @@ -40,18 +38,21 @@ import alfio.repository.EventRepository; import alfio.repository.system.ConfigurationRepository; import com.github.benmanes.caffeine.cache.Cache; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpSession; +import java.math.BigInteger; +import java.security.SecureRandom; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; @@ -61,16 +62,16 @@ import static alfio.model.system.ConfigurationKeys.*; import static alfio.model.system.ConfigurationPathLevel.*; -import static java.util.stream.Collectors.toList; @Transactional -@Log4j2 -@RequiredArgsConstructor public class ConfigurationManager { + private static final Logger log = LoggerFactory.getLogger(ConfigurationManager.class); + private static final Map<ConfigurationKeys.SettingCategory, List<Configuration>> ORGANIZATION_CONFIGURATION = collectConfigurationKeysByCategory(ORGANIZATION); private static final Map<ConfigurationKeys.SettingCategory, List<Configuration>> EVENT_CONFIGURATION = collectConfigurationKeysByCategory(ConfigurationPathLevel.EVENT); private static final Map<ConfigurationKeys.SettingCategory, List<Configuration>> CATEGORY_CONFIGURATION = collectConfigurationKeysByCategory(ConfigurationPathLevel.TICKET_CATEGORY); + private static final String DELETE_ERROR = "User is not owner of the organization. Therefore, delete is not allowed."; private final ConfigurationRepository configurationRepository; private final UserManager userManager; @@ -78,34 +79,50 @@ public class ConfigurationManager { private final ExternalConfiguration externalConfiguration; private final Environment environment; private final Cache<Set<ConfigurationKeys>, Map<ConfigurationKeys, MaybeConfiguration>> oneMinuteCache; + private final SecureRandom secureRandom = new SecureRandom(); + + public ConfigurationManager(ConfigurationRepository configurationRepository, + UserManager userManager, + EventRepository eventRepository, + ExternalConfiguration externalConfiguration, + Environment environment, + Cache<Set<ConfigurationKeys>, + Map<ConfigurationKeys, MaybeConfiguration>> oneMinuteCache) { + this.configurationRepository = configurationRepository; + this.userManager = userManager; + this.eventRepository = eventRepository; + this.externalConfiguration = externalConfiguration; + this.environment = environment; + this.oneMinuteCache = oneMinuteCache; + } //TODO: refactor, not the most beautiful code, find a better solution... private Optional<Configuration> findByConfigurationPathAndKey(ConfigurationPath path, ConfigurationKeys key) { var keyAsString = key.getValue(); var configList = new ArrayList<>(externalConfiguration.load(keyAsString)); switch (path.pathLevel()) { - case SYSTEM: + case SYSTEM -> { configList.addAll(configurationRepository.findByKeyAtSystemLevel(keyAsString)); return selectPath(configList); - case ORGANIZATION: { + } + case ORGANIZATION -> { OrganizationConfigurationPath o = (OrganizationConfigurationPath) path; configList.addAll(configurationRepository.findByOrganizationAndKey(o.getId(), key.getValue())); return selectPath(configList); } - case EVENT: { + case EVENT -> { EventConfigurationPath o = (EventConfigurationPath) path; configList.addAll(configurationRepository.findByEventAndKey(o.getOrganizationId(), o.getId(), keyAsString)); return selectPath(configList); } - case TICKET_CATEGORY: { + case TICKET_CATEGORY -> { TicketCategoryConfigurationPath o = (TicketCategoryConfigurationPath) path; configList.addAll(configurationRepository.findByTicketCategoryAndKey(o.getOrganizationId(), o.getEventId(), o.getId(), keyAsString)); return selectPath(configList); } - default: - throw new IllegalStateException("Can't reach here"); + default -> throw new IllegalStateException("Can't reach here"); } } @@ -124,19 +141,16 @@ private Optional<Configuration> selectPath(List<Configuration> conf) { public void saveConfig(ConfigurationPathKey pathKey, String value) { ConfigurationPath path = pathKey.getPath(); switch (path.pathLevel()) { - case SYSTEM: - saveSystemConfiguration(pathKey.getKey(), value); - break; - case ORGANIZATION: + case SYSTEM -> saveSystemConfiguration(pathKey.getKey(), value); + case ORGANIZATION -> { OrganizationConfigurationPath orgPath = (OrganizationConfigurationPath) path; saveOrganizationConfiguration(orgPath.getId(), pathKey.getKey().name(), value); - break; - case EVENT: + } + case EVENT -> { EventConfigurationPath eventPath = (EventConfigurationPath) path; saveEventConfiguration(eventPath.getId(), eventPath.getOrganizationId(), pathKey.getKey().name(), value); - break; - default: - throw new IllegalStateException("can't reach here"); + } + default -> throw new IllegalStateException("can't reach here"); } } @@ -223,7 +237,7 @@ public void saveSystemConfiguration(ConfigurationKeys key, String value) { Optional<Configuration> conf = findByConfigurationPathAndKey(Configuration.system(), key); if(key.isBooleanComponentType()) { Optional<Boolean> state = getThreeStateValue(value); - if(conf.isPresent()) { + if(conf.filter(c -> c.getConfigurationPathLevel() != EXTERNAL).isPresent()) { if(state.isPresent()) { configurationRepository.update(key.getValue(), value); } else { @@ -257,7 +271,7 @@ public boolean isBasicConfigurationNeeded() { boolean absent = externalConfiguration.getSingle(key.getValue()) .or(() -> configurationRepository.findOptionalByKey(key.getValue())).isEmpty(); if (absent) { - log.warn("cannot find a value for " + key.getValue()); + log.warn("cannot find a value for {}", key.getValue()); } return absent; }); @@ -279,8 +293,7 @@ public Map<ConfigurationKeys.SettingCategory, List<Configuration>> loadOrganizat List<SettingCategory> toBeRemoved = PaymentProxy.availableProxies() .stream() .filter(pp -> paymentMethodsBlacklist.contains(pp.getKey())) - .flatMap(pp -> pp.getSettingCategories().stream()) - .collect(toList()); + .flatMap(pp -> pp.getSettingCategories().stream()).toList(); if(toBeRemoved.isEmpty()) { return result; @@ -377,7 +390,7 @@ private Map<ConfigurationKeys.SettingCategory, List<Configuration>> groupByCateg ConfigurationKeys.SettingCategory key = e.getKey(); Set<Configuration> entries = new TreeSet<>(e.getValue()); if(existing.containsKey(key)) { - List<Configuration> configurations = existing.get(key).stream().filter(Predicate.not(Configuration::isInternal)).collect(Collectors.toList()); + List<Configuration> configurations = existing.get(key).stream().filter(Predicate.not(Configuration::isInternal)).toList(); configurations.forEach(entries::remove); entries.addAll(configurations); } @@ -391,13 +404,11 @@ public Map<ConfigurationKeys.SettingCategory, List<Configuration>> loadAllSystem return Collections.emptyMap(); } final List<Configuration> existing = configurationRepository.findSystemConfiguration() - .stream() - .filter(c -> !ConfigurationKeys.fromString(c.getKey()).isInternal()) - .collect(toList()); + .stream() + .filter(c -> !ConfigurationKeys.fromString(c.getKey()).isInternal()).toList(); final List<Configuration> missing = Arrays.stream(ConfigurationKeys.visible()) - .filter(k -> existing.stream().noneMatch(c -> c.getKey().equals(k.getValue()))) - .map(mapEmptyKeys(ConfigurationPathLevel.SYSTEM)) - .collect(toList()); + .filter(k -> existing.stream().noneMatch(c -> c.getKey().equals(k.getValue()))) + .map(mapEmptyKeys(ConfigurationPathLevel.SYSTEM)).toList(); List<Configuration> result = new ArrayList<>(existing); result.addAll(missing); return result.stream().sorted().collect(groupByCategory()); @@ -416,21 +427,21 @@ public void deleteKey(String key) { } public void deleteOrganizationLevelByKey(String key, int organizationId, String username) { - Validate.isTrue(userManager.isOwnerOfOrganization(userManager.findUserByUsername(username), organizationId), "User is not owner of the organization. Therefore, delete is not allowed."); + Validate.isTrue(userManager.isOwnerOfOrganization(userManager.findUserByUsername(username), organizationId), DELETE_ERROR); configurationRepository.deleteOrganizationLevelByKey(key, organizationId); } public void deleteEventLevelByKey(String key, int eventId, String username) { EventAndOrganizationId event = eventRepository.findEventAndOrganizationIdById(eventId); Validate.notNull(event, "Wrong event id"); - Validate.isTrue(userManager.isOwnerOfOrganization(userManager.findUserByUsername(username), event.getOrganizationId()), "User is not owner of the organization. Therefore, delete is not allowed."); + Validate.isTrue(userManager.isOwnerOfOrganization(userManager.findUserByUsername(username), event.getOrganizationId()), DELETE_ERROR); configurationRepository.deleteEventLevelByKey(key, eventId); } public void deleteCategoryLevelByKey(String key, int eventId, int categoryId, String username) { EventAndOrganizationId event = eventRepository.findEventAndOrganizationIdById(eventId); Validate.notNull(event, "Wrong event id"); - Validate.isTrue(userManager.isOwnerOfOrganization(userManager.findUserByUsername(username), event.getOrganizationId()), "User is not owner of the organization. Therefore, delete is not allowed."); + Validate.isTrue(userManager.isOwnerOfOrganization(userManager.findUserByUsername(username), event.getOrganizationId()), DELETE_ERROR); configurationRepository.deleteCategoryLevelByKey(key, eventId, categoryId); } @@ -483,6 +494,13 @@ public boolean isRecaptchaForOfflinePaymentAndFreeEnabled(ConfigurationLevel con } // https://github.com/alfio-event/alf.io/issues/573 + + public boolean canAttachBillingDocumentToConfirmationEmail(Configurable configurable) { + var config = getFor(List.of(ENABLE_ITALY_E_INVOICING, ITALY_E_INVOICING_SEND_PROFORMA), configurable.getConfigurationLevel()); + return !isItalianEInvoicingEnabled(config) + || config.get(ITALY_E_INVOICING_SEND_PROFORMA).getValueAsBooleanOrDefault(); + } + public boolean canGenerateReceiptOrInvoiceToCustomer(Configurable configurable) { return !isItalianEInvoicingEnabled(configurable); } @@ -532,23 +550,21 @@ public MaybeConfiguration getFor(ConfigurationKeys key, ConfigurationLevel confi public Map<ConfigurationKeys, MaybeConfiguration> getFor(Collection<ConfigurationKeys> keys, ConfigurationLevel configurationLevel) { var keysAsString = keys.stream().map(ConfigurationKeys::getValue).collect(Collectors.toSet()); List<ConfigurationKeyValuePathLevel> found = new ArrayList<>(externalConfiguration.getAll(keysAsString)); - switch(configurationLevel.getPathLevel()) { - case SYSTEM: - found.addAll(configurationRepository.findByKeysAtSystemLevel(keysAsString)); - break; - case ORGANIZATION: - found.addAll(configurationRepository.findByOrganizationAndKeys(((OrganizationLevel)configurationLevel).organizationId, keysAsString)); - break; - case EVENT: + switch (configurationLevel.getPathLevel()) { + case SYSTEM -> found.addAll(configurationRepository.findByKeysAtSystemLevel(keysAsString)); + case ORGANIZATION -> + found.addAll(configurationRepository.findByOrganizationAndKeys(((OrganizationLevel) configurationLevel).organizationId, keysAsString)); + case EVENT -> { var eventLevel = (EventLevel) configurationLevel; found.addAll(configurationRepository.findByEventAndKeys(eventLevel.organizationId, eventLevel.eventId, keysAsString)); - break; - case TICKET_CATEGORY: + } + case TICKET_CATEGORY -> { var categoryLevel = (CategoryLevel) configurationLevel; found.addAll(configurationRepository.findByTicketCategoryAndKeys(categoryLevel.organizationId, categoryLevel.eventId, categoryLevel.categoryId, keysAsString)); - break; - default: - break; + } + default -> { + // ignore EXTERNAL + } } return buildKeyConfigurationMapResult(keys, found); } @@ -579,11 +595,13 @@ public List<PaymentMethod> getBlacklistedMethodsForReservation(PurchaseContext p return categoryIds.stream() .filter(blacklistForCategories::containsKey) .flatMap(id -> Arrays.stream(blacklistForCategories.get(id).split(","))) + .filter(StringUtils::isNotBlank) .map(name -> PaymentProxy.valueOf(name).getPaymentMethod()) - .collect(toList()); - } else if (categoryIds.size() > 0) { + .toList(); + } else if (!categoryIds.isEmpty()) { return configurationRepository.findByKeyAtCategoryLevel(e.getId(), e.getOrganizationId(), IterableUtils.get(categoryIds, 0), PAYMENT_METHODS_BLACKLIST.name()) - .map(v -> Arrays.stream(v.getValue().split(",")).map(name -> PaymentProxy.valueOf(name).getPaymentMethod()).collect(toList())) + .filter(v -> StringUtils.isNotBlank(v.getValue())) + .map(v -> Arrays.stream(v.getValue().split(",")).map(name -> PaymentProxy.valueOf(name).getPaymentMethod()).toList()) .orElse(List.of()); } else { return List.<PaymentMethod>of(); @@ -595,6 +613,17 @@ private static boolean toBeSaved(ConfigurationModification c) { return Optional.ofNullable(c.getId()).orElse(-1) > -1 || !StringUtils.isBlank(c.getValue()); } + public List<Integer> getCategoriesWithNoTaxes(List<Integer> categoriesIds) { + if (categoriesIds.isEmpty()) { + return List.of(); + } + return configurationRepository.getCategoriesWithFlag(categoriesIds, APPLY_TAX_TO_CATEGORY.name(), BooleanUtils.FALSE); + } + + public boolean noTaxesFlagDefinedFor(List<TicketCategory> categories) { + return !getCategoriesWithNoTaxes(categories.stream().map(TicketCategory::getId).toList()).isEmpty(); + } + public static class MaybeConfiguration { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private final Optional<ConfigurationKeyValuePathLevel> configuration; @@ -689,7 +718,10 @@ public AlfioInfo getInfo(HttpSession session) { ENABLE_EU_VAT_DIRECTIVE, COUNTRY_OF_BUSINESS, ENABLE_REVERSE_CHARGE_IN_PERSON, - ENABLE_REVERSE_CHARGE_ONLINE); + ENABLE_REVERSE_CHARGE_ONLINE, + ANNOUNCEMENT_BANNER_CONTENT, + ENABLE_WALLET, + ENABLE_PASS); var conf = getFor(options, ConfigurationLevel.system()); var analyticsConf = AnalyticsConfiguration.build(conf, session); @@ -700,7 +732,9 @@ public AlfioInfo getInfo(HttpSession session) { analyticsConf, conf.get(GLOBAL_PRIVACY_POLICY).getValueOrNull(), conf.get(GLOBAL_TERMS).getValueOrNull(), - PurchaseContextInfoBuilder.invoicingInfo(this, conf)); + PurchaseContextInfoBuilder.invoicingInfo(this, conf), + StringUtils.trimToNull(conf.get(ANNOUNCEMENT_BANNER_CONTENT).getValueOrNull()), + new WalletConfiguration(conf.get(ENABLE_WALLET).getValueAsBooleanOrDefault(), conf.get(ENABLE_PASS).getValueAsBooleanOrDefault())); } public Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> getPublicOpenIdConfiguration() { @@ -717,4 +751,25 @@ public String baseUrl(PurchaseContext purchaseContext) { .orElseGet(() -> ConfigurationLevel.organization(purchaseContext.getOrganizationId())); return StringUtils.removeEnd(getFor(BASE_URL, configurationLevel).getRequiredValue(), "/"); } + + public String retrieveSystemApiKey(boolean rotate) { + Optional<Configuration> existing = configurationRepository.findOptionalByKey(SYSTEM_API_KEY.name()); + String apiKeyValue; + if(existing.isPresent() && rotate) { + apiKeyValue = generateApiKey(); + configurationRepository.update(SYSTEM_API_KEY.name(), apiKeyValue); + } else if(existing.isPresent()) { + apiKeyValue = existing.get().getValue(); + } else { + apiKeyValue = generateApiKey(); + configurationRepository.insert(SYSTEM_API_KEY.name(), apiKeyValue, SYSTEM_API_KEY.getDescription()); + } + return apiKeyValue; + } + + private String generateApiKey() { + var bytes = new byte[48]; + secureRandom.nextBytes(bytes); + return new BigInteger(1, bytes).toString(36); + } } diff --git a/src/main/java/alfio/manager/system/DataMigrator.java b/src/main/java/alfio/manager/system/DataMigrator.java index 55b9d882fd..11f86bfa96 100644 --- a/src/main/java/alfio/manager/system/DataMigrator.java +++ b/src/main/java/alfio/manager/system/DataMigrator.java @@ -27,10 +27,11 @@ import alfio.repository.system.EventMigrationRepository; import alfio.util.ClockProvider; import alfio.util.MonetaryUtil; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource; @@ -57,9 +58,10 @@ @Component @Transactional(readOnly = true) -@Log4j2 public class DataMigrator { + private static final Logger log = LoggerFactory.getLogger(DataMigrator.class); + private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d\\.)([0-9.]*)(-SNAPSHOT)?"); private static final Map<String, String> PRICE_UPDATE_BY_KEY = new LinkedHashMap<>(); private final EventMigrationRepository eventMigrationRepository; @@ -288,8 +290,14 @@ private void fillDescriptions(Event event) { * in order to ensure backward compatibility */ private void fixAvailableSeats(Event event) { - int availableSeats = eventRepository.countExistingTickets(event.getId()); - eventRepository.updatePrices(event.getCurrency(), availableSeats, event.isVatIncluded(), event.getVat(), event.getAllowedPaymentProxies().stream().map(PaymentProxy::name).collect(joining(",")), event.getId(), event.getVatStatus(), event.getSrcPriceCts()); + try { + int availableSeats = eventRepository.countExistingTickets(event.getId()); + var paymentProxies = event.getAllowedPaymentProxies().stream().map(PaymentProxy::name).collect(joining(",")); + eventRepository.updatePrices(event.getCurrency(), availableSeats, event.isVatIncluded(), + event.getVat(), paymentProxies, event.getId(), event.getVatStatus(), event.getSrcPriceCts()); + } catch (Exception ex) { + log.trace("got exception while fixing available seats", ex); + } } boolean needsFixing(EventMigration eventMigration) { @@ -439,7 +447,7 @@ public VatStatus getVatStatus() { @Override public Optional<PromoCodeDiscount> getDiscount() { return Optional.ofNullable(ticket.get("discount_amount")) - .map(amount -> new PromoCodeDiscount(0, "", eventId, null, null, null, (int) amount, PromoCodeDiscount.DiscountType.valueOf((String) ticket.get("discount_type")), "", null, null, null, PromoCodeDiscount.CodeType.DISCOUNT, null)); + .map(amount -> new PromoCodeDiscount(0, "", eventId, null, null, null, (int) amount, PromoCodeDiscount.DiscountType.valueOf((String) ticket.get("discount_type")), "", null, null, null, PromoCodeDiscount.CodeType.DISCOUNT, null, currencyCode)); } }); }) diff --git a/src/main/java/alfio/manager/system/DefaultMailer.java b/src/main/java/alfio/manager/system/DefaultMailer.java index 513a705068..89b0a2e41c 100644 --- a/src/main/java/alfio/manager/system/DefaultMailer.java +++ b/src/main/java/alfio/manager/system/DefaultMailer.java @@ -17,6 +17,7 @@ package alfio.manager.system; import alfio.model.Configurable; +import alfio.repository.user.OrganizationRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; @@ -35,16 +36,19 @@ public class DefaultMailer implements Mailer { private final Environment environment; @Autowired - public DefaultMailer(ConfigurationManager configurationManager, Environment environment, HttpClient httpClient) { + public DefaultMailer(ConfigurationManager configurationManager, + Environment environment, + HttpClient httpClient, + OrganizationRepository organizationRepository) { this.configurationManager = configurationManager; this.environment = environment; this.mailers = new HashMap<>(); - this.defaultMailer = new SmtpMailer(configurationManager); + this.defaultMailer = new SmtpMailer(configurationManager, organizationRepository); mailers.put("smtp", defaultMailer); - mailers.put("mailgun", new MailgunMailer(httpClient, configurationManager)); - mailers.put("mailjet", new MailjetMailer(httpClient, configurationManager)); - mailers.put("sendgrid", new SendGridMailer(httpClient, configurationManager)); - mailers.put("disabled", new MockMailer(configurationManager, environment)); + mailers.put("mailgun", new MailgunMailer(httpClient, configurationManager, organizationRepository)); + mailers.put("mailjet", new MailjetMailer(httpClient, configurationManager, organizationRepository)); + mailers.put("sendgrid", new SendGridMailer(httpClient, configurationManager, organizationRepository)); + mailers.put("disabled", new MockMailer(configurationManager, environment, organizationRepository)); } @Override diff --git a/src/main/java/alfio/manager/system/ExternalConfiguration.java b/src/main/java/alfio/manager/system/ExternalConfiguration.java index e2f5a055cb..d309bcfd94 100644 --- a/src/main/java/alfio/manager/system/ExternalConfiguration.java +++ b/src/main/java/alfio/manager/system/ExternalConfiguration.java @@ -26,8 +26,6 @@ import alfio.model.system.ConfigurationKeyValuePathLevel; import alfio.model.system.ConfigurationPathLevel; import lombok.Data; -import lombok.Getter; -import lombok.Setter; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.tuple.Pair; @@ -46,14 +44,28 @@ @Component @Profile("!"+ Initializer.PROFILE_INTEGRATION_TEST) @ConfigurationProperties("alfio.override.system") -@Getter -@Setter public class ExternalConfiguration { private static final String EXTERNAL_EXTENSION_PATH = "::EXTERNAL::"; private static final int EXTERNAL_CONFIGURATION_ID = Integer.MIN_VALUE; private Map<String, String> settings = new HashMap<>(); private List<ExtensionOverride> extensions = new ArrayList<>(); + public Map<String, String> getSettings() { + return settings; + } + + public void setSettings(Map<String, String> settings) { + this.settings = settings; + } + + public List<ExtensionOverride> getExtensions() { + return extensions; + } + + public void setExtensions(List<ExtensionOverride> extensions) { + this.extensions = extensions; + } + public List<Configuration> load(String key) { return getSingle(key).map(List::of).orElse(List.of()); } diff --git a/src/main/java/alfio/manager/system/Mailer.java b/src/main/java/alfio/manager/system/Mailer.java index be9c23471b..547ad5a94f 100644 --- a/src/main/java/alfio/manager/system/Mailer.java +++ b/src/main/java/alfio/manager/system/Mailer.java @@ -26,6 +26,8 @@ public interface Mailer { + String SKIP_PASSBOOK = "skipPassbook"; + void send(Configurable configurable, String fromName, String to, List<String> cc, String subject, String text, Optional<String> html, Attachment... attachment); @Data @@ -75,7 +77,7 @@ public String fileName(String fileName) { public String contentType(String contentType) { return "application/vnd.apple.pkpass"; } - }; + }, SUBSCRIPTION_PDF; public List<AttachmentIdentifier> reinterpretAs() { return Collections.emptyList(); diff --git a/src/main/java/alfio/manager/system/MailgunMailer.java b/src/main/java/alfio/manager/system/MailgunMailer.java index 3471f49108..0726efbcf6 100644 --- a/src/main/java/alfio/manager/system/MailgunMailer.java +++ b/src/main/java/alfio/manager/system/MailgunMailer.java @@ -17,12 +17,13 @@ package alfio.manager.system; import alfio.model.Configurable; +import alfio.model.system.ConfigurationKeys; +import alfio.repository.user.OrganizationRepository; import alfio.util.HttpUtils; -import alfio.util.oauth2.AccessTokenResponseDetails; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -35,15 +36,29 @@ import static alfio.model.system.ConfigurationKeys.*; -@Log4j2 -@AllArgsConstructor -class MailgunMailer implements Mailer { +class MailgunMailer extends BaseMailer { + + private static final Logger log = LoggerFactory.getLogger(MailgunMailer.class); private final HttpClient client; private final ConfigurationManager configurationManager; + MailgunMailer(HttpClient client, + ConfigurationManager configurationManager, + OrganizationRepository organizationRepository) { + super(organizationRepository); + this.client = client; + this.configurationManager = configurationManager; + } + - private static Map<String, String> getEmailData(String from, String to, String replyTo, List<String> cc, String subject, String text, Optional<String> html) { + + private static Map<String, String> getEmailData(String from, + String to, + List<String> cc, + String subject, + String text, + Optional<String> html) { Map<String, String> emailData = new HashMap<>(Map.of( "from", from, "to", to, @@ -54,9 +69,6 @@ private static Map<String, String> getEmailData(String from, String to, String r if(cc != null && !cc.isEmpty()) { emailData.put("cc", StringUtils.join(cc, ',')); } - if(StringUtils.isNoneBlank(replyTo)) { - emailData.put("h:Reply-To", replyTo); - } html.ifPresent(htmlContent -> emailData.put("html", htmlContent)); return emailData; } @@ -65,7 +77,15 @@ private static Map<String, String> getEmailData(String from, String to, String r public void send(Configurable configurable, String fromName, String to, List<String> cc, String subject, String text, Optional<String> html, Attachment... attachment) { - var conf = configurationManager.getFor(Set.of(MAILGUN_KEY, MAILGUN_DOMAIN, MAILGUN_EU, MAILGUN_FROM, MAIL_REPLY_TO), configurable.getConfigurationLevel()); + var conf = configurationManager.getFor( + Set.of( + MAILGUN_KEY, + MAILGUN_DOMAIN, + MAILGUN_EU, + MAILGUN_FROM, + MAIL_REPLY_TO, + MAIL_SET_ORG_REPLY_TO), + configurable.getConfigurationLevel()); String apiKey = conf.get(MAILGUN_KEY).getRequiredValue(); String domain = conf.get(MAILGUN_DOMAIN).getRequiredValue(); @@ -76,9 +96,10 @@ public void send(Configurable configurable, String fromName, String to, List<Str var from = fromName + " <" + conf.get(MAILGUN_FROM).getRequiredValue() +">"; - var replyTo = conf.get(MAIL_REPLY_TO).getValueOrDefault(""); + var emailData = getEmailData(from, to, cc, subject, text, html); - var emailData = getEmailData(from, to, replyTo, cc, subject, text, html); + setReplyToIfPresent(conf, configurable.getOrganizationId(), + replyTo -> emailData.put("h:Reply-To", replyTo)); var requestBuilder = HttpRequest.newBuilder() .uri(URI.create(baseUrl + domain + "/messages")) diff --git a/src/main/java/alfio/manager/system/MailjetMailer.java b/src/main/java/alfio/manager/system/MailjetMailer.java index 3b9199a96a..df5ee24c16 100644 --- a/src/main/java/alfio/manager/system/MailjetMailer.java +++ b/src/main/java/alfio/manager/system/MailjetMailer.java @@ -17,11 +17,12 @@ package alfio.manager.system; import alfio.model.Configurable; +import alfio.repository.user.OrganizationRepository; import alfio.util.HttpUtils; import alfio.util.Json; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URI; @@ -29,17 +30,20 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.*; -import java.util.stream.Collectors; import static alfio.model.system.ConfigurationKeys.*; -@Log4j2 -public class MailjetMailer implements Mailer { +class MailjetMailer extends BaseMailer { + + private static final Logger log = LoggerFactory.getLogger(MailjetMailer.class); private final HttpClient client; private final ConfigurationManager configurationManager; - public MailjetMailer(HttpClient httpClient, ConfigurationManager configurationManager) { + MailjetMailer(HttpClient httpClient, + ConfigurationManager configurationManager, + OrganizationRepository organizationRepository) { + super(organizationRepository); this.client = httpClient; this.configurationManager = configurationManager; } @@ -47,7 +51,8 @@ public MailjetMailer(HttpClient httpClient, ConfigurationManager configurationMa @Override public void send(Configurable configurable, String fromName, String to, List<String> cc, String subject, String text, Optional<String> html, Attachment... attachment) { - var conf = configurationManager.getFor(EnumSet.of(MAILJET_APIKEY_PUBLIC, MAILJET_APIKEY_PRIVATE, MAILJET_FROM, MAIL_REPLY_TO), configurable.getConfigurationLevel()); + var conf = configurationManager.getFor( + EnumSet.of(MAILJET_APIKEY_PUBLIC, MAILJET_APIKEY_PRIVATE, MAILJET_FROM, MAIL_REPLY_TO, MAIL_SET_ORG_REPLY_TO), configurable.getConfigurationLevel()); String apiKeyPublic = conf.get(MAILJET_APIKEY_PUBLIC).getRequiredValue(); @@ -60,7 +65,7 @@ public void send(Configurable configurable, String fromName, String to, List<Str List<Map<String, String>> recipients = new ArrayList<>(); recipients.add(Collections.singletonMap("Email", to)); if(cc != null && !cc.isEmpty()) { - recipients.addAll(cc.stream().map(email -> Collections.singletonMap("Email", email)).collect(Collectors.toList())); + recipients.addAll(cc.stream().map(email -> Collections.singletonMap("Email", email)).toList()); } mailPayload.put("FromEmail", fromEmail); @@ -70,13 +75,11 @@ public void send(Configurable configurable, String fromName, String to, List<Str html.ifPresent(h -> mailPayload.put("Html-part", h)); mailPayload.put("Recipients", recipients); - String replyTo = conf.get(MAIL_REPLY_TO).getValueOrDefault(""); - if(StringUtils.isNotBlank(replyTo)) { - mailPayload.put("Headers", Collections.singletonMap("Reply-To", replyTo)); - } + setReplyToIfPresent(conf, configurable.getOrganizationId(), + address -> mailPayload.put("Headers", Collections.singletonMap("Reply-To", address))); if(attachment != null && attachment.length > 0) { - mailPayload.put("Attachments", Arrays.stream(attachment).map(MailjetMailer::fromAttachment).collect(Collectors.toList())); + mailPayload.put("Attachments", Arrays.stream(attachment).map(MailjetMailer::fromAttachment).toList()); } HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.mailjet.com/v3/send")) @@ -88,7 +91,7 @@ public void send(Configurable configurable, String fromName, String to, List<Str try { HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding()); if(!HttpUtils.callSuccessful(response)) { - log.warn("sending email was not successful:" + response); + log.warn("sending email was not successful: {}", response); throw new IllegalStateException("Attempt to send a message failed. Result is: "+response.statusCode()); } } catch (IOException e) { diff --git a/src/main/java/alfio/manager/system/MockMailer.java b/src/main/java/alfio/manager/system/MockMailer.java index 0c1d141a31..161e21dacf 100644 --- a/src/main/java/alfio/manager/system/MockMailer.java +++ b/src/main/java/alfio/manager/system/MockMailer.java @@ -17,25 +17,33 @@ package alfio.manager.system; import alfio.model.Configurable; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import alfio.repository.user.OrganizationRepository; import org.springframework.core.env.Environment; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static alfio.model.system.ConfigurationKeys.MAIL_REPLY_TO; +import static alfio.model.system.ConfigurationKeys.MAIL_SET_ORG_REPLY_TO; -@Log4j2 -@AllArgsConstructor -public class MockMailer implements Mailer { +class MockMailer extends BaseMailer { + + private static final Logger log = LoggerFactory.getLogger(MockMailer.class); private final ConfigurationManager configurationManager; private final Environment environment; + MockMailer(ConfigurationManager configurationManager, + Environment environment, + OrganizationRepository organizationRepository) { + super(organizationRepository); + this.configurationManager = configurationManager; + this.environment = environment; + } + @Override public void send(Configurable configurable, String fromName, String to, List<String> cc, String subject, String text, Optional<String> html, Attachment... attachments) { @@ -47,9 +55,13 @@ public void send(Configurable configurable, String fromName, String to, List<Str .stream().map(a -> "{filename:" +a.getFilename() + ", contentType: " + a.getContentType() + "}") .collect(Collectors.joining(", ")); + var conf = configurationManager.getFor(EnumSet.of(MAIL_REPLY_TO, MAIL_SET_ORG_REPLY_TO), configurable.getConfigurationLevel()); + var replyTo = new AtomicReference<String>(null); + setReplyToIfPresent(conf, configurable.getOrganizationId(), replyTo::set); + log.info("Email: from: {}, replyTo: {}, to: {}, cc: {}, subject: {}, text: {}, html: {}, attachments: {}", fromName, - configurationManager.getFor(MAIL_REPLY_TO, configurable.getConfigurationLevel()).getValueOrDefault(""), + replyTo.get(), to, cc, subject, text, html.orElse("no html"), printedAttachments); } diff --git a/src/main/java/alfio/manager/system/ReservationPriceCalculator.java b/src/main/java/alfio/manager/system/ReservationPriceCalculator.java index f28b81d4e7..b2dafa26ef 100644 --- a/src/main/java/alfio/manager/system/ReservationPriceCalculator.java +++ b/src/main/java/alfio/manager/system/ReservationPriceCalculator.java @@ -22,7 +22,6 @@ import alfio.model.decorator.TicketPriceContainer; import alfio.model.subscription.Subscription; import alfio.util.MonetaryUtil; -import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.tuple.Pair; import java.math.BigDecimal; @@ -33,7 +32,7 @@ import static org.apache.commons.lang3.ObjectUtils.firstNonNull; -@RequiredArgsConstructor + public class ReservationPriceCalculator implements PriceContainer { final TicketReservation reservation; final PromoCodeDiscount discount; @@ -44,6 +43,24 @@ public class ReservationPriceCalculator implements PriceContainer { private final List<Subscription> subscriptions; private final Optional<Subscription> appliedSubscription; + public ReservationPriceCalculator(TicketReservation reservation, + PromoCodeDiscount discount, + List<Ticket> tickets, + List<AdditionalServiceItem> additionalServiceItems, + List<AdditionalService> additionalServices, + PurchaseContext purchaseContext, + List<Subscription> subscriptions, + Optional<Subscription> appliedSubscription) { + this.reservation = reservation; + this.discount = discount; + this.tickets = tickets; + this.additionalServiceItems = additionalServiceItems; + this.additionalServices = additionalServices; + this.purchaseContext = purchaseContext; + this.subscriptions = subscriptions; + this.appliedSubscription = appliedSubscription; + } + @Override public int getSrcPriceCts() { return tickets.stream().mapToInt(this::getTicketSrcPriceCts).sum() + @@ -62,7 +79,10 @@ public BigDecimal getAppliedDiscount() { if (discount.getDiscountType() == PromoCodeDiscount.DiscountType.FIXED_AMOUNT_RESERVATION) { return MonetaryUtil.centsToUnit(discount.getDiscountAmount() + subscriptionDiscount, reservation.getCurrencyCode()); } - return MonetaryUtil.centsToUnit(tickets.stream().mapToInt(Ticket::getDiscountCts).sum() + + int ticketDiscount = tickets.stream() + .mapToInt(t -> MonetaryUtil.unitToCents(TicketPriceContainer.from(t, reservation.getVatStatus(), reservation.getVatPercentageOrZero(), purchaseContext.getVatStatus(), discount).getAppliedDiscount(), reservation.getCurrencyCode())) + .sum(); + return MonetaryUtil.centsToUnit(ticketDiscount + additionalServiceItems.stream().mapToInt(AdditionalServiceItem::getDiscountCts).sum() + subscriptions.stream().mapToInt(Subscription::getDiscountCts).sum() + subscriptionDiscount, reservation.getCurrencyCode()); } @@ -115,7 +135,7 @@ public static ReservationPriceCalculator from(TicketReservation reservation, Pro } private int getTicketSrcPriceCts(Ticket t) { - if(t.getVatStatus() == VatStatus.INCLUDED_EXEMPT || t.getVatStatus() == VatStatus.NOT_INCLUDED_EXEMPT) { + if(VatStatus.isVatExempt(t.getVatStatus())) { return t.getSrcPriceCts() - Math.abs(t.getVatCts()); // VAT can be negative in some cases } return t.getSrcPriceCts(); diff --git a/src/main/java/alfio/manager/system/SendGridMailer.java b/src/main/java/alfio/manager/system/SendGridMailer.java index dac0a82e82..da2cb54796 100644 --- a/src/main/java/alfio/manager/system/SendGridMailer.java +++ b/src/main/java/alfio/manager/system/SendGridMailer.java @@ -18,12 +18,13 @@ import alfio.model.Configurable; import alfio.model.system.ConfigurationKeys; +import alfio.repository.user.OrganizationRepository; import alfio.util.HttpUtils; import alfio.util.Json; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import java.io.IOException; @@ -32,21 +33,30 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; -@Log4j2 -@AllArgsConstructor -public class SendGridMailer implements Mailer { +class SendGridMailer extends BaseMailer { + + private static final Logger log = LoggerFactory.getLogger(SendGridMailer.class); private static final String EMAIL = "email"; - private final HttpClient client; + private final HttpClient client; private final ConfigurationManager configurationManager; + SendGridMailer(HttpClient client, + ConfigurationManager configurationManager, + OrganizationRepository organizationRepository) { + super(organizationRepository); + this.client = client; + this.configurationManager = configurationManager; + } + @Override public void send(Configurable configurable, final String fromName, final String to, final List<String> cc, final String subject, final String text, final Optional<String> html, final Attachment... attachment) { - final var config = configurationManager.getFor(Set.of(ConfigurationKeys.SENDGRID_API_KEY, ConfigurationKeys.SENDGRID_FROM, ConfigurationKeys.MAIL_REPLY_TO), configurable.getConfigurationLevel()); + final var config = configurationManager.getFor(EnumSet.of( + ConfigurationKeys.SENDGRID_API_KEY, ConfigurationKeys.SENDGRID_FROM, ConfigurationKeys.MAIL_REPLY_TO, ConfigurationKeys.MAIL_SET_ORG_REPLY_TO), + configurable.getConfigurationLevel()); final var from = config.get(ConfigurationKeys.SENDGRID_FROM).getRequiredValue(); final var personalizations = createPersonalizations(to, cc, subject); final var contents = createContents(text, html); @@ -57,6 +67,8 @@ public void send(Configurable configurable, final String fromName, final String payload.put("from", Map.of(EMAIL, from, "name", fromName)); payload.put("personalizations", personalizations); payload.put("content", contents); + setReplyToIfPresent(config, configurable.getOrganizationId(), + replyTo -> payload.put("reply_to", Map.of(EMAIL, replyTo))); //prepare request final var body = Json.GSON.toJson(payload); final var request = HttpRequest.newBuilder(URI.create("https://api.sendgrid.com/v3/mail/send")) @@ -81,7 +93,7 @@ private List<Map<String, Object>> createPersonalizations(final String to, final final var recipients = new ArrayList<>(); recipients.add(Map.of(EMAIL, to)); if (CollectionUtils.isNotEmpty(cc)) { - recipients.addAll(cc.stream().map(email -> Map.of(EMAIL, email)).collect(Collectors.toList())); + recipients.addAll(cc.stream().map(email -> Map.of(EMAIL, email)).toList()); } return List.of(Map.of("to", recipients, "subject", subject)); } @@ -95,7 +107,7 @@ private List<Map<String, String>> createContents(final String text, final Option private void addAttachments(final Map<String, Object> payload, final Attachment[] attachment) { final var attachments = Stream.of(attachment) - .map(attach -> Map.of("filename", attach.getFilename(), "content", attach.getSource(), "content_id", attach.getIdentifier().name(), "type", attach.getContentType())).collect(Collectors.toList()); + .map(attach -> Map.of("filename", attach.getFilename(), "content", attach.getSource(), "content_id", attach.getIdentifier().name(), "type", attach.getContentType())).toList(); payload.put("attachments", attachments); } } diff --git a/src/main/java/alfio/manager/system/SmtpMailer.java b/src/main/java/alfio/manager/system/SmtpMailer.java index 32c72248db..9de7ae83cd 100644 --- a/src/main/java/alfio/manager/system/SmtpMailer.java +++ b/src/main/java/alfio/manager/system/SmtpMailer.java @@ -18,10 +18,11 @@ import alfio.model.Configurable; import alfio.model.system.ConfigurationKeys; -import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; +import alfio.repository.user.OrganizationRepository; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertiesLoaderUtils; @@ -37,35 +38,43 @@ import javax.mail.internet.MimeMessage; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.*; import static alfio.model.system.ConfigurationKeys.*; +import static java.nio.charset.StandardCharsets.UTF_8; -@Log4j2 -@AllArgsConstructor -class SmtpMailer implements Mailer { - +class SmtpMailer extends BaseMailer { + + private static final Logger log = LoggerFactory.getLogger(SmtpMailer.class); private final ConfigurationManager configurationManager; + SmtpMailer(ConfigurationManager configurationManager, + OrganizationRepository organizationRepository) { + super(organizationRepository); + this.configurationManager = configurationManager; + } + @Override public void send(Configurable configurable, String fromName, String to, List<String> cc, String subject, String text, Optional<String> html, Attachment... attachments) { var conf = configurationManager.getFor(Set.of(SMTP_FROM_EMAIL, MAIL_REPLY_TO, - SMTP_HOST, SMTP_PORT, SMTP_PROTOCOL, + MAIL_SET_ORG_REPLY_TO, SMTP_HOST, SMTP_PORT, SMTP_PROTOCOL, SMTP_USERNAME, SMTP_PASSWORD, SMTP_PROPERTIES), configurable.getConfigurationLevel()); MimeMessagePreparator preparator = mimeMessage -> { - MimeMessageHelper message = html.isPresent() || !ArrayUtils.isEmpty(attachments) ? new MimeMessageHelper(mimeMessage, true, "UTF-8") - : new MimeMessageHelper(mimeMessage, "UTF-8"); + MimeMessageHelper message = html.isPresent() || !ArrayUtils.isEmpty(attachments) ? new MimeMessageHelper(mimeMessage, true, UTF_8.name()) + : new MimeMessageHelper(mimeMessage, UTF_8.name()); message.setSubject(subject); message.setFrom(conf.get(SMTP_FROM_EMAIL).getRequiredValue(), fromName); - String replyTo = conf.get(MAIL_REPLY_TO).getValueOrDefault(""); - if(StringUtils.isNotBlank(replyTo)) { - message.setReplyTo(replyTo); - } + setReplyToIfPresent(conf, configurable.getOrganizationId(), replyTo -> { + try { + message.setReplyTo(replyTo); + } catch (MessagingException e) { + throw new RuntimeException(e); + } + }); message.setTo(to); if(cc != null && !cc.isEmpty()){ message.setCc(cc.toArray(new String[0])); @@ -90,7 +99,7 @@ public void send(Configurable configurable, String fromName, String to, List<Str private static JavaMailSender toMailSender(Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> conf) { JavaMailSenderImpl r = new CustomJavaMailSenderImpl(); - r.setDefaultEncoding("UTF-8"); + r.setDefaultEncoding(UTF_8.name()); r.setHost(conf.get(SMTP_HOST).getRequiredValue()); r.setPort(Integer.parseInt(conf.get(SMTP_PORT).getRequiredValue())); @@ -103,7 +112,7 @@ private static JavaMailSender toMailSender(Map<ConfigurationKeys, ConfigurationM if (properties != null) { try { Properties prop = PropertiesLoaderUtils.loadProperties(new EncodedResource(new ByteArrayResource( - properties.getBytes(StandardCharsets.UTF_8)), "UTF-8")); + properties.getBytes(UTF_8)), UTF_8.name())); r.setJavaMailProperties(prop); } catch (IOException e) { log.warn("error while setting the mail sender properties", e); diff --git a/src/main/java/alfio/manager/user/PublicUserManager.java b/src/main/java/alfio/manager/user/PublicUserManager.java index 35b599ef65..8095b51ca8 100644 --- a/src/main/java/alfio/manager/user/PublicUserManager.java +++ b/src/main/java/alfio/manager/user/PublicUserManager.java @@ -32,7 +32,6 @@ import alfio.model.user.User; import alfio.repository.TicketFieldRepository; import alfio.repository.user.UserRepository; -import lombok.AllArgsConstructor; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; import org.springframework.security.core.Authentication; @@ -49,13 +48,22 @@ @Component @Transactional -@AllArgsConstructor public class PublicUserManager { private final UserRepository userRepository; private final ExtensionManager extensionManager; private final UserManager userManager; private final TicketFieldRepository ticketFieldRepository; + public PublicUserManager(UserRepository userRepository, + ExtensionManager extensionManager, + UserManager userManager, + TicketFieldRepository ticketFieldRepository) { + this.userRepository = userRepository; + this.extensionManager = extensionManager; + this.userManager = userManager; + this.ticketFieldRepository = ticketFieldRepository; + } + public boolean deleteUserProfile(OpenIdAlfioAuthentication authentication) { return userManager.findOptionalEnabledUserByUsername(authentication.getName()) .filter(u -> u.getType() == User.Type.PUBLIC) diff --git a/src/main/java/alfio/manager/user/UserManager.java b/src/main/java/alfio/manager/user/UserManager.java index 429b8fb345..3b97e6d3e4 100644 --- a/src/main/java/alfio/manager/user/UserManager.java +++ b/src/main/java/alfio/manager/user/UserManager.java @@ -16,7 +16,7 @@ */ package alfio.manager.user; -import alfio.model.TicketReservationAdditionalInfo; +import alfio.config.authentication.support.APITokenAuthentication; import alfio.model.modification.OrganizationModification; import alfio.model.result.ValidationResult; import alfio.model.user.*; @@ -29,12 +29,13 @@ import alfio.util.PasswordGenerator; import alfio.util.RequestUtils; import ch.digitalfondue.npjt.AffectedRowCountAndKey; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -47,16 +48,16 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static alfio.config.authentication.support.AuthenticationConstants.SYSTEM_API_CLIENT; import static java.util.Objects.requireNonNull; -import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.toList; @Component @Transactional -@RequiredArgsConstructor -@Log4j2 public class UserManager { + private static final Logger log = LoggerFactory.getLogger(UserManager.class); + public static final String ADMIN_USERNAME = "admin"; private static final Pattern SLUG_VALIDATOR = Pattern.compile("^[A-Za-z-_0-9]+$"); private final AuthorityRepository authorityRepository; @@ -66,10 +67,29 @@ public class UserManager { private final PasswordEncoder passwordEncoder; private final InvoiceSequencesRepository invoiceSequencesRepository; + private final FindByIndexNameSessionRepository<?> sessionsByPrincipalFinder; + + public UserManager(AuthorityRepository authorityRepository, + OrganizationRepository organizationRepository, + UserOrganizationRepository userOrganizationRepository, + UserRepository userRepository, + PasswordEncoder passwordEncoder, + InvoiceSequencesRepository invoiceSequencesRepository, + FindByIndexNameSessionRepository<?> sessionsByPrincipalFinder) { + this.authorityRepository = authorityRepository; + this.organizationRepository = organizationRepository; + this.userOrganizationRepository = userOrganizationRepository; + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.invoiceSequencesRepository = invoiceSequencesRepository; + this.sessionsByPrincipalFinder = sessionsByPrincipalFinder; + } + private List<Authority> getUserAuthorities(User user) { return authorityRepository.findGrantedAuthorities(user.getUsername()); } + @Transactional(readOnly = true) public List<UserWithOrganizations> findAllUsers(String username) { List<Organization> organizations = findUserOrganizations(username); Predicate<Collection<?>> isNotEmpty = ks -> !ks.isEmpty(); @@ -78,49 +98,61 @@ public List<UserWithOrganizations> findAllUsers(String username) { .flatMap(org -> { Map<Integer, List<UserOrganization>> usersAndOrganizations = userOrganizationRepository.findByOrganizationIdsOrderByUserId(organizations.stream().map(Organization::getId).collect(toList())) .stream() - .collect(Collectors.groupingBy(UserOrganization::getUserId)); + .collect(Collectors.groupingBy(UserOrganization::userId)); return Optional.of(usersAndOrganizations.keySet()) .filter(isNotEmpty) .map(ks -> userRepository.findByIds(ks) .stream() .map(u -> { List<UserOrganization> userOrganizations = usersAndOrganizations.get(u.getId()); - List<Organization> filteredOrganizations = organizations.stream().filter(o -> userOrganizations.stream().anyMatch(uo -> uo.getOrganizationId() == o.getId())).collect(toList()); + List<Organization> filteredOrganizations = organizations.stream().filter(o -> userOrganizations.stream().anyMatch(uo -> uo.organizationId() == o.getId())).collect(toList()); List<Role> roles = authorityRepository.findRoles(u.getUsername()).stream().map(Role::fromRoleName).collect(toList()); return new UserWithOrganizations(u, filteredOrganizations, roles); }).collect(toList())); }).orElseGet(Collections::emptyList); } + @Transactional(readOnly = true) public List<User> findAllEnabledUsers(String username) { return findUserOrganizations(username) .stream() .flatMap(o -> userOrganizationRepository.findByOrganizationId(o.getId()).stream()) - .map(uo -> userRepository.findById(uo.getUserId())) + .map(uo -> userRepository.findById(uo.userId())) .filter(User::isEnabled) .collect(toList()); } + @Transactional(readOnly = true) public List<User> findAllApiKeysFor(int organizationId) { return userRepository.findAllApiKeysForOrganization(organizationId); } + @Transactional(readOnly = true) public User findUserByUsername(String username) { return userRepository.findEnabledByUsername(username).orElseThrow(IllegalArgumentException::new); } + @Transactional(readOnly = true) public Optional<User> findOptionalEnabledUserByUsername(String username) { return userRepository.findEnabledByUsername(username); } + @Transactional(readOnly = true) public boolean usernameExists(String username) { return userRepository.findIdByUserName(username).isPresent(); } - public User findUser(int id) { + @Transactional(readOnly = true) + public User findUser(int id, Principal principal) { + checkAccessToUserId(principal, id); + return internalFindUser(id); + } + + private User internalFindUser(int id) { return userRepository.findById(id); } + @Transactional(readOnly = true) public Collection<Role> getAvailableRoles(String username) { User user = findUserByUsername(username); return isAdmin(user) || isOwner(user) ? EnumSet.of(Role.OWNER, Role.OPERATOR, Role.SUPERVISOR, Role.SPONSOR, Role.API_CONSUMER) : Collections.emptySet(); @@ -131,34 +163,45 @@ public Collection<Role> getAvailableRoles(String username) { * @param user * @return user role */ + @Transactional(readOnly = true) public Role getUserRole(User user) { return getUserAuthorities(user).stream().map(Authority::getRole).sorted().findFirst().orElse(Role.OPERATOR); } + @Transactional(readOnly = true) public List<Organization> findUserOrganizations(String username) { return organizationRepository.findAllForUser(username); } + @Transactional(readOnly = true) public Organization findOrganizationById(int id, String username) { + return findOptionalOrganizationById(id, username).orElseThrow(IllegalArgumentException::new); + } + + @Transactional(readOnly = true) + public Optional<Organization> findOptionalOrganizationById(int id, String username) { return findUserOrganizations(username) - .stream() - .filter(o -> o.getId() == id) - .findFirst() - .orElseThrow(IllegalArgumentException::new); + .stream() + .filter(o -> o.getId() == id) + .findFirst(); } + @Transactional(readOnly = true) public boolean isAdmin(User user) { return checkRole(user, Collections.singleton(Role.ADMIN)); } + @Transactional(readOnly = true) public boolean isOwner(User user) { return checkRole(user, EnumSet.of(Role.ADMIN, Role.OWNER, Role.API_CONSUMER)); } + @Transactional(readOnly = true) public boolean isOwnerOfOrganization(User user, int organizationId) { - return isAdmin(user) || (isOwner(user) && userOrganizationRepository.findByUserId(user.getId()).stream().anyMatch(uo -> uo.getOrganizationId() == organizationId)); + return isAdmin(user) || (isOwner(user) && userOrganizationRepository.findByUserId(user.getId()).stream().anyMatch(uo -> uo.organizationId() == organizationId)); } + @Transactional(readOnly = true) public boolean isOwnerOfOrganization(String username, int organizationId) { return userRepository.findByUsername(username) .filter(user -> isOwnerOfOrganization(user, organizationId)) @@ -170,7 +213,10 @@ private boolean checkRole(User user, Set<Role> expectedRoles) { return authorityRepository.checkRole(user.getUsername(), roleNames); } - public int createOrganization(OrganizationModification om) { + public int createOrganization(OrganizationModification om, Principal principal) { + // + checkIsAdmin(principal); + // var affectedRowNumAndKey = organizationRepository.create(om.getName(), om.getDescription(), om.getEmail(), om.getExternalId(), om.getSlug()); int orgId = affectedRowNumAndKey.getKey(); Validate.isTrue(invoiceSequencesRepository.initFor(orgId) == 2); @@ -178,8 +224,12 @@ public int createOrganization(OrganizationModification om) { } public void updateOrganization(OrganizationModification om, Principal principal) { - boolean isAdmin = RequestUtils.isAdmin(principal); - var currentOrg = organizationRepository.getById(requireNonNull(om.getId())); + // + int orgId = requireNonNull(om.getId()); + checkAccessToOrganizationId(principal, orgId); + // + boolean isAdmin = RequestUtils.isAdmin(principal) || RequestUtils.isSystemApiKey(principal); + var currentOrg = organizationRepository.getById(orgId); organizationRepository.update(om.getId(), om.getName(), om.getDescription(), @@ -188,6 +238,7 @@ public void updateOrganization(OrganizationModification om, Principal principal) isAdmin ? om.getSlug() : currentOrg.getSlug()); } + @Transactional(readOnly = true) public ValidationResult validateOrganizationSlug(OrganizationModification om, Principal principal) { if(!RequestUtils.isAdmin(principal)) { return ValidationResult.failed(new ValidationResult.ErrorDescriptor("slug", "Cannot update Organizer URL.")); @@ -202,6 +253,7 @@ public ValidationResult validateOrganizationSlug(OrganizationModification om, Pr return ValidationResult.success(); } + @Transactional(readOnly = true) public ValidationResult validateOrganization(OrganizationModification om, Principal principal) { if(om.getId() == null && organizationRepository.findByName(om.getName()).isPresent()) { return ValidationResult.failed(new ValidationResult.ErrorDescriptor("name", "There is already another organization with the same name.")); @@ -218,7 +270,11 @@ public ValidationResult validateOrganization(OrganizationModification om, Princi return ValidationResult.success(); } - public void editUser(int id, int organizationId, String username, String firstName, String lastName, String emailAddress, String description, Role role, String currentUsername) { + public void editUser(int id, int organizationId, String username, String firstName, String lastName, String emailAddress, String description, Role role, Principal principal) { + // + checkAccessToUserIdAndNewOrganization(principal, id, organizationId); + // + String currentUsername = principal.getName(); boolean admin = ADMIN_USERNAME.equals(username) && Role.ADMIN == role; if(!admin) { int userOrganizationResult = userOrganizationRepository.updateUserOrganization(id, organizationId); @@ -233,16 +289,17 @@ public void editUser(int id, int organizationId, String username, String firstNa } } - public void updateUserContactInfo(int id, String firstName, String lastName, String emailAddress) { + public void updateCurrentUserContactInfo(String firstName, String lastName, String emailAddress, Principal principal) { + var id = userRepository.findIdByUserName(principal.getName()).orElseThrow(); userRepository.updateContactInfo(id, firstName, lastName, emailAddress); } - public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType) { - return insertUser(organizationId, username, firstName, lastName, emailAddress, role, userType, null, null); + public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType, Principal principal) { + return insertUser(organizationId, username, firstName, lastName, emailAddress, role, userType, null, null, principal); } - public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType, ZonedDateTime validTo, String description) { + public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType, ZonedDateTime validTo, String description, Principal principal) { if (userType == User.Type.API_KEY) { username = UUID.randomUUID().toString(); firstName = "apikey"; @@ -251,17 +308,20 @@ public UserWithPassword insertUser(int organizationId, String username, String f } String userPassword = PasswordGenerator.generateRandomPassword(); - return insertUser(organizationId, username, firstName, lastName, emailAddress, role, userType, userPassword, validTo, description); + return insertUser(organizationId, username, firstName, lastName, emailAddress, role, userType, userPassword, validTo, description, principal); } - public void bulkInsertApiKeys(int organizationId, Role role, List<String> descriptions) { + public void bulkInsertApiKeys(int organizationId, Role role, List<String> descriptions, Principal principal) { for (String description : descriptions) { - insertUser(organizationId, null, null, null, null, role, User.Type.API_KEY, null, description); + insertUser(organizationId, null, null, null, null, role, User.Type.API_KEY, null, description, principal); } } - public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType, String userPassword, ZonedDateTime validTo, String description) { + public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType, String userPassword, ZonedDateTime validTo, String description, Principal principal) { + // + checkAccessToOrganizationId(principal, organizationId); + // Organization organization = organizationRepository.getById(organizationId); AffectedRowCountAndKey<Integer> result = userRepository.create(username, passwordEncoder.encode(userPassword), firstName, lastName, emailAddress, true, userType, validTo, description); userOrganizationRepository.create(result.getKey(), organization.getId()); @@ -270,34 +330,62 @@ public UserWithPassword insertUser(int organizationId, String username, String f } - public UserWithPassword resetPassword(int userId) { - User user = findUser(userId); + public UserWithPassword resetPassword(int userId, Principal principal) { + // + checkAccessToUserId(principal, userId); + // + User user = internalFindUser(userId); String password = PasswordGenerator.generateRandomPassword(); Validate.isTrue(userRepository.resetPassword(userId, passwordEncoder.encode(password)) == 1, "error during password reset"); + + if (!Objects.requireNonNull(principal).getName().equals(user.getUsername())) { + invalidateSessionsForUser(user.getUsername()); + } + return new UserWithPassword(user, password, UUID.randomUUID().toString()); } - public void updatePassword(String username, String newPassword) { + public void updateCurrentUserPassword(String newPassword, Principal principal) { + var username = principal.getName(); User user = userRepository.findByUsername(username).orElseThrow(IllegalStateException::new); Validate.isTrue(PasswordGenerator.isValid(newPassword), "invalid password"); Validate.isTrue(userRepository.resetPassword(user.getId(), passwordEncoder.encode(newPassword)) == 1, "error during password update"); } - public void deleteUser(int userId, String currentUsername) { + public void deleteUser(int userId, Principal principal) { + // + checkAccessToUserId(principal, userId); + // + var currentUsername = principal.getName(); User currentUser = userRepository.findEnabledByUsername(currentUsername).orElseThrow(IllegalArgumentException::new); Assert.isTrue(userId != currentUser.getId(), "sorry but you cannot delete your own account."); + var userToDelete = userRepository.findById(userId); userRepository.deleteUserAndReferences(userId); + invalidateSessionsForUser(userToDelete.getUsername()); + } + + private void invalidateSessionsForUser(String username) { + var sessionsToInvalidate = sessionsByPrincipalFinder.findByPrincipalName(username).keySet(); + sessionsToInvalidate.forEach(sessionsByPrincipalFinder::deleteById); } - public void enable(int userId, String currentUsername, boolean status) { + public void enable(int userId, boolean status, Principal principal) { + // + checkAccessToUserId(principal, userId); + // + var currentUsername = principal.getName(); User currentUser = userRepository.findEnabledByUsername(currentUsername).orElseThrow(IllegalArgumentException::new); Assert.isTrue(userId != currentUser.getId(), "sorry but you cannot commit suicide"); - userRepository.toggleEnabled(userId, status); + if (!status) { // disable user + var userToDisable = userRepository.findById(userId); + invalidateSessionsForUser(userToDisable.getUsername()); + } } + @Transactional(readOnly = true) public ValidationResult validateUser(Integer id, String username, String firstName, String lastName, String emailAddress) { Optional<User> existing = Optional.ofNullable(id).flatMap(userRepository::findOptionalById); @@ -311,6 +399,7 @@ public ValidationResult validateUser(Integer id, String username, String firstNa .collect(toList())); } + @Transactional(readOnly = true) public ValidationResult validateNewPassword(String username, String oldPassword, String newPassword, String newPasswordConfirm) { return userRepository.findByUsername(username) .map(u -> { @@ -345,4 +434,66 @@ public Integer createPublicUserIfNotExists(String username, String email, String return userRepository.findIdByUserName(username).orElse(null); } + + private void checkIsAdmin(Principal principal) { + if (principal == null) { + return; + } + if (isSystemApiUser(principal)) { + log.trace("Allowing call for System API Key"); + return; + } + if (isAdmin(findUserByUsername(principal.getName()))) { + return; + } + log.warn("User {} is not an admin", principal.getName()); + throw new IllegalArgumentException("User " + principal.getName() + " is not an admin"); + } + + private void checkAccessToUserId(Principal principal, int userId) { + if (principal == null) { + return; + } + var currentUser = findUserByUsername(principal.getName()); + if (isAdmin(currentUser)) { + return; + } + var targetUser = internalFindUser(userId); + var targetUserOrgs = findUserOrganizations(targetUser.getUsername()); + Assert.state(!targetUser.getUsername().equals(ADMIN_USERNAME) && !isAdmin(targetUser), "Targeted user cannot be admin"); + Assert.isTrue(targetUserOrgs.size() == 1, "Targeted user can only be in one organization"); + for (var org : targetUserOrgs) { + if (isOwnerOfOrganization(currentUser, org.getId())) { + return; + } + } + log.warn("User {} does not have access to userId {}", principal.getName(), userId); + throw new IllegalStateException("User " + principal.getName() + " does not have access to userId " + userId); + } + + private void checkAccessToUserIdAndNewOrganization(Principal principal, int userId, int newOrganization) { + checkAccessToUserId(principal, userId); + checkAccessToOrganizationId(principal, newOrganization); + } + + private void checkAccessToOrganizationId(Principal principal, int organizationId) { + if (principal == null) { + return; + } + if (isSystemApiUser(principal)) { + log.trace("Allowing access to Organization " + organizationId + " to System API Key"); + return; + } + if (isOwnerOfOrganization(principal.getName(), organizationId)) { + return; + } + log.warn("User {} don't have access to organizationId {}", principal.getName(), organizationId); + throw new IllegalArgumentException("User " + principal.getName() + " don't have access to organizationId " + organizationId); + } + + private boolean isSystemApiUser(Principal principal) { + return principal instanceof APITokenAuthentication + && ((APITokenAuthentication)principal).getAuthorities().stream() + .allMatch(authority -> authority.getAuthority().equals("ROLE_" + SYSTEM_API_CLIENT)); + } } diff --git a/src/main/java/alfio/manager/wallet/EventTicketClass.java b/src/main/java/alfio/manager/wallet/EventTicketClass.java new file mode 100644 index 0000000000..6873efc8da --- /dev/null +++ b/src/main/java/alfio/manager/wallet/EventTicketClass.java @@ -0,0 +1,154 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +@Builder +@Getter +public class EventTicketClass implements WalletEntity { + + public static final String WALLET_URL = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketClass"; + + private static final DateTimeFormatter EVENT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSX"); + + private String id; + + private String eventName; + + private String eventOrGroupingId; + + private String description; + + private String venue; + + private LatitudeLongitudePoint location; + + private String ticketType; + + private String logoUri; + + private ZonedDateTime start; + + private ZonedDateTime end; + + public String build(ObjectMapper mapper) { + ObjectNode object = mapper.createObjectNode(); + object.put("id", id); + object.put("eventId", eventOrGroupingId); + object.put("multipleDevicesAndHoldersAllowedStatus", "ONE_USER_ALL_DEVICES"); + object.put("issuerName", eventName); + object.set("eventName", + mapper.createObjectNode().set("defaultValue", mapper.createObjectNode() + .put("language", "en-US") + .put("value", ticketType))); + object.set("venue", + mapper.createObjectNode().setAll( + Map.of( + "name", mapper.createObjectNode().set("defaultValue", mapper.createObjectNode() + .put("language", "en-US") + .put("value", venue)), + "address", mapper.createObjectNode().set("defaultValue", mapper.createObjectNode() + .put("language", "en-US") + .put("value", venue)) + ))); + object.set("dateTime", + mapper.createObjectNode() + .put("start", start.format(EVENT_TIME_FORMATTER)) + .put("end", end.format(EVENT_TIME_FORMATTER))); + object.put("reviewStatus", "UNDER_REVIEW"); + object.put("hexBackgroundColor", "#FFFFFF"); + object.set("logo", + mapper.createObjectNode().set("sourceUri", mapper.createObjectNode() + .put("uri", logoUri))); + object.set("classTemplateInfo", + mapper.createObjectNode().set("cardTemplateOverride", + mapper.createObjectNode().set("cardRowTemplateInfos", + mapper.createArrayNode() + .add(mapper.createObjectNode().set("oneItem", + mapper.createObjectNode().setAll( + Map.of( + "item", mapper.createObjectNode().set("firstValue", + mapper.createObjectNode().set("fields", + mapper.createArrayNode() + .add(mapper.createObjectNode().put("fieldPath", "class.dateTime.start")))))))) + .add(mapper.createObjectNode().set("oneItem", + mapper.createObjectNode().setAll( + Map.of( + "item", mapper.createObjectNode().set("firstValue", + mapper.createObjectNode().set("fields", + mapper.createArrayNode() + .add(mapper.createObjectNode().put("fieldPath", "class.dateTime.end")))))))) + ))); + object.set("linksModuleData", + mapper.createObjectNode().set("uris", + mapper.createArrayNode() + .add(mapper.createObjectNode() + .put("uri", "https://alf.io") + .put("description", "Powered by Alf.io, the Open Source ticket reservation system.") + .put("id", "alfio"))) + ); + object.set("messages", mapper.createArrayNode() + .add(mapper.createObjectNode() + .put("header", "Event Description") + .put("body", description)) + ); + if (location != null) { + object.set("locations", location.accept(mapper)); + } + + try { + return mapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Getter + public static class LatitudeLongitudePoint { + private final Double latitude; + + private final Double longitude; + + private LatitudeLongitudePoint(Double latitude, Double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + public static LatitudeLongitudePoint of(Double latitude, Double longitude) { + return new LatitudeLongitudePoint(latitude, longitude); + } + + private ArrayNode accept(ObjectMapper mapper) { + return mapper.createArrayNode() + .add(mapper.createObjectNode() + .put("latitude", latitude) + .put("longitude", longitude) + ); + } + } +} diff --git a/src/main/java/alfio/manager/wallet/EventTicketObject.java b/src/main/java/alfio/manager/wallet/EventTicketObject.java new file mode 100644 index 0000000000..a20221913f --- /dev/null +++ b/src/main/java/alfio/manager/wallet/EventTicketObject.java @@ -0,0 +1,64 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import org.springframework.core.io.ByteArrayResource; + +import java.net.URI; +import java.time.format.DateTimeFormatter; + +@Builder +@Getter +public class EventTicketObject implements WalletEntity { + + public static final String WALLET_URL = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketObject"; + + private String id; + + private String classId; + + private String ticketHolderName; + + private String ticketNumber; + + private String barcode; + + public String build(ObjectMapper mapper) { + ObjectNode object = mapper.createObjectNode(); + object.put("id", id); + object.put("classId", classId); + object.put("state", "ACTIVE"); + object.put("ticketHolderName", ticketHolderName); + object.put("ticketNumber", ticketNumber); + object.set("barcode", mapper.createObjectNode() + .put("type", "QR_CODE") + .put("value", barcode)); + + try { + return mapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/alfio/manager/wallet/GoogleWalletException.java b/src/main/java/alfio/manager/wallet/GoogleWalletException.java new file mode 100644 index 0000000000..9e35d24257 --- /dev/null +++ b/src/main/java/alfio/manager/wallet/GoogleWalletException.java @@ -0,0 +1,29 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +public class GoogleWalletException extends RuntimeException { + + public GoogleWalletException(String message) { + super(message); + } + + public GoogleWalletException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/alfio/manager/wallet/GoogleWalletManager.java b/src/main/java/alfio/manager/wallet/GoogleWalletManager.java new file mode 100644 index 0000000000..e44def6b50 --- /dev/null +++ b/src/main/java/alfio/manager/wallet/GoogleWalletManager.java @@ -0,0 +1,349 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +import alfio.manager.system.ConfigurationManager; +import alfio.model.*; +import alfio.model.metadata.TicketMetadata; +import alfio.model.metadata.TicketMetadataContainer; +import alfio.model.system.ConfigurationKeys; +import alfio.model.system.command.InvalidateAccess; +import alfio.repository.*; +import alfio.util.HttpUtils; +import alfio.util.MustacheCustomTag; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.security.interfaces.RSAPrivateKey; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static alfio.config.Initializer.*; +import static alfio.model.system.ConfigurationKeys.*; + +@Component +@AllArgsConstructor +@Transactional +public class GoogleWalletManager { + + private static final Logger log = LoggerFactory.getLogger(GoogleWalletManager.class); + private static final String WALLET_OBJECT_ID = "gWalletObjectId"; + private static final String AUTHORIZATION = "Authorization"; + private static final String BEARER_PLACEHOLDER = "Bearer %s"; + private final EventRepository eventRepository; + private final ConfigurationManager configurationManager; + private final EventDescriptionRepository eventDescriptionRepository; + private final TicketCategoryRepository ticketCategoryRepository; + private final TicketRepository ticketRepository; + private final HttpClient httpClient; + private final ObjectMapper objectMapper; + private final Environment environment; + private final AuditingRepository auditingRepository; + + public Optional<Pair<EventAndOrganizationId, Ticket>> validateTicket(String eventName, String ticketUuid) { + var eventOptional = eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName); + if (eventOptional.isEmpty()) { + log.trace("event {} not found", eventName); + return Optional.empty(); + } + + var event = eventOptional.get(); + return ticketRepository.findOptionalByUUID(ticketUuid) + .filter(t -> t.getEventId() == event.getId()) + .map(t -> Pair.of(event, t)); + } + + public String createAddToWalletUrl(Ticket ticket, EventAndOrganizationId event) { + Map<ConfigurationKeys, String> passConf = getConfigurationKeys(event); + if (!passConf.isEmpty()) { + return buildWalletPassUrl(ticket, eventRepository.findById(event.getId()), passConf); + } else { + throw new GoogleWalletException("Google Wallet integration is not enabled."); + } + } + + @EventListener + public void invalidateAccessForTicket(InvalidateAccess invalidateAccess) { + try { + Map<ConfigurationKeys, String> passConf = getConfigurationKeys(invalidateAccess.getEvent()); + if (!passConf.isEmpty()) { + invalidateAccess.getTicketMetadataContainer() + .getMetadataForKey(TicketMetadataContainer.GENERAL) + .map(m -> m.getAttributes().get(WALLET_OBJECT_ID)) + .ifPresent(s -> invalidateObject(invalidateAccess.getTicket().getUuid(), s, passConf)); + } + } catch (Exception e) { + log.warn("Error while invalidating access for ticket " + invalidateAccess.getTicket().getUuid(), e); + } + } + + private void invalidateObject(String ticketId, String objectId, Map<ConfigurationKeys, String> passConf) { + try { + log.trace("Invalidating access to object ID: {}", objectId); + var credentials = retrieveCredentials(passConf.get(WALLET_SERVICE_ACCOUNT_KEY)); + URI uriWithId = URI.create(String.format("%s/%s", EventTicketObject.WALLET_URL, objectId)); + HttpRequest expireRequest = HttpRequest.newBuilder() + .uri(uriWithId) + .header(AUTHORIZATION, String.format(BEARER_PLACEHOLDER, credentials.refreshAccessToken().getTokenValue())) + .method("PATCH", HttpRequest.BodyPublishers.ofString("{\"state\":\"INACTIVE\"}")) + .build(); + var response = httpClient.send(expireRequest, HttpResponse.BodyHandlers.ofString()); + if (HttpUtils.callSuccessful(response)) { + log.debug("Access invalidated for ticket {}", ticketId); + } else { + logIfWarnEnabled(() -> log.warn("Cannot invalidate access for ticket {}, response: {}", ticketId, response.body())); + } + } catch (IOException e) { + throw new GoogleWalletException(e.getMessage(), e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new GoogleWalletException(e.getMessage(), e); + } + } + + private Map<ConfigurationKeys, String> getConfigurationKeys(EventAndOrganizationId event) { + var conf = configurationManager.getFor(Set.of( + ENABLE_WALLET, + WALLET_ISSUER_IDENTIFIER, + WALLET_SERVICE_ACCOUNT_KEY, + WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS, + BASE_URL), + event.getConfigurationLevel()); + + if (!conf.get(ENABLE_WALLET).getValueAsBooleanOrDefault()) { + return Map.of(); + } + var configValues = Map.of( + WALLET_ISSUER_IDENTIFIER, conf.get(WALLET_ISSUER_IDENTIFIER).getValue(), + WALLET_SERVICE_ACCOUNT_KEY, conf.get(WALLET_SERVICE_ACCOUNT_KEY).getValue(), + WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS, conf.get(WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS).getValue(), + BASE_URL, conf.get(BASE_URL).getValue()); + + if (configValues.values().stream().anyMatch(Optional::isEmpty)) { + return Map.of(); + } + + return configValues + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().orElseThrow())); + } + + private String buildWalletPassUrl(Ticket ticket, + Event event, + Map<ConfigurationKeys, String> config) { + String baseUrl = config.get(BASE_URL); + String issuerId = config.get(WALLET_ISSUER_IDENTIFIER); + String serviceAccountKey = config.get(WALLET_SERVICE_ACCOUNT_KEY); + boolean overwritePreviousClassesAndEvents = Boolean.parseBoolean(config.get(WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS)); + + String eventDescription = MustacheCustomTag.renderToTextCommonmark( + eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(event.getId(), EventDescription.EventDescriptionType.DESCRIPTION, ticket.getUserLanguage()).orElse("")); + TicketCategory category = ticketCategoryRepository.getById(ticket.getCategoryId()); + var ticketValidityStart = Optional.ofNullable(category.getTicketValidityStart(event.getZoneId())).orElse(event.getBegin()); + var ticketValidityEnd = Optional.ofNullable(category.getTicketValidityEnd(event.getZoneId())).orElse(event.getEnd()); + + EventTicketClass.LatitudeLongitudePoint latitudeLongitudePoint = null; + if (event.getLatitude() != null && event.getLongitude() != null) { + latitudeLongitudePoint = EventTicketClass.LatitudeLongitudePoint.of(Double.parseDouble(event.getLatitude()), Double.parseDouble(event.getLongitude())); + } + + var host = URI.create(baseUrl).getHost().replace(".", "-"); + var eventTicketClass = EventTicketClass.builder() + .id(formatEventTicketClassId(event, issuerId, category, host)) + .eventOrGroupingId(Integer.toString(event.getId())) + .logoUri(baseUrl + "/file/" + event.getFileBlobId()) + .eventName(event.getDisplayName()) + .description(eventDescription) + .venue(event.getLocation()) + .location(latitudeLongitudePoint) + .ticketType(category.getName()) + .start(ticketValidityStart) + .end(ticketValidityEnd) + .build(); + + String walletTicketId = formatEventTicketObjectId(ticket, event, issuerId, host); + var eventTicketObject = EventTicketObject.builder() + .id(walletTicketId) + .classId(eventTicketClass.getId()) + .ticketHolderName(ticket.getFullName()) + .ticketNumber(ticket.getUuid()) + .barcode(ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive())) + .build(); + + GoogleCredentials credentials = retrieveCredentials(serviceAccountKey); + + createEventClass(credentials, eventTicketClass, overwritePreviousClassesAndEvents); + String eventObjectId = createEventObject(credentials, eventTicketObject, overwritePreviousClassesAndEvents); + String walletPassUrl = generateWalletPassUrl(credentials, eventObjectId, baseUrl); + persistPassId(ticket, eventObjectId); + return walletPassUrl; + } + + private static GoogleCredentials retrieveCredentials(String serviceAccountKey) { + try { + return GoogleCredentials + .fromStream(new ByteArrayInputStream(serviceAccountKey.getBytes(StandardCharsets.UTF_8))) + .createScoped(Collections.singleton("https://www.googleapis.com/auth/wallet_object.issuer")); + } catch (IOException e) { + throw new GoogleWalletException("Unable to retrieve Service Account Credentials from configuration", e); + } + } + + private void persistPassId(Ticket ticket, String eventObjectId) { + var metadataContainer = ticketRepository.getTicketMetadata(ticket.getId()); + var existingMetadata = metadataContainer.getMetadataForKey(TicketMetadataContainer.GENERAL); + var attributesMap = existingMetadata.map(ticketMetadata -> new HashMap<>(ticketMetadata.getAttributes())) + .orElseGet(HashMap::new); + attributesMap.put(WALLET_OBJECT_ID, eventObjectId); + metadataContainer.putMetadata(TicketMetadataContainer.GENERAL, existingMetadata.map(tm -> tm.withAttributes(attributesMap)).orElseGet(() -> new TicketMetadata(null, null, attributesMap))); + ticketRepository.updateTicketMetadata(ticket.getId(), metadataContainer); + } + + private String formatEventTicketObjectId(Ticket ticket, Event event, String issuerId, String host) { + return String.format("%s.%s-%s-object.%s-%s", + issuerId, + walletIdPrefix(), + host, + event.getShortName().replaceAll("[^\\w.-]", "_"), + // if the attendee gives their ticket to somebody else, the new ticket holder must be able to add their ticket to the wallet + // therefore we add count(audit(UPDATE_TICKET)) as suffix for the ticket UUID + ticket.getUuid()+ "_" + auditingRepository.countAuditsOfTypeForTicket(ticket.getTicketsReservationId(), ticket.getId(), Audit.EventType.UPDATE_TICKET)); + } + + private String formatEventTicketClassId(Event event, String issuerId, TicketCategory category, String host) { + return String.format("%s.%s-%s-class.%s-%d", issuerId, walletIdPrefix(), host, event.getShortName().replaceAll("[^\\w.-]", "_"), category.getId()); + } + + private String generateWalletPassUrl(GoogleCredentials credentials, String eventObjectId, String url) { + var objectIdMap = Map.of("id", eventObjectId); + var payload = Map.of("genericObjects", List.of(objectIdMap)); + var claims = Map.of( + "iss", ((ServiceAccountCredentials) credentials).getClientEmail(), + "aud", "google", + "origins", List.of(url), + "typ", "savetowallet", + "payload", payload + ); + + Algorithm algorithm = Algorithm.RSA256( + null, + (RSAPrivateKey) ((ServiceAccountCredentials) credentials).getPrivateKey()); + String token = JWT.create() + .withPayload(claims) + .sign(algorithm); + return String.format("https://pay.google.com/gp/v/save/%s", token); + } + + private String createEventClass(GoogleCredentials credentials, EventTicketClass eventTicketClass, boolean overwritePreviousClassesAndEvents) { + return createOnWallet(EventTicketClass.WALLET_URL, credentials, eventTicketClass, overwritePreviousClassesAndEvents); + } + + private String createEventObject(GoogleCredentials credentials, EventTicketObject eventTicketObject, boolean overwritePreviousClassesAndEvents) { + return createOnWallet(EventTicketObject.WALLET_URL, credentials, eventTicketObject, overwritePreviousClassesAndEvents); + } + + private String createOnWallet(String uri, GoogleCredentials credentials, WalletEntity entity, boolean overwritePreviousClassesAndEvents) { + try { + URI uriWithId = URI.create(String.format("%s/%s", uri, entity.getId())); + HttpRequest getRequest = HttpRequest.newBuilder() + .uri(uriWithId) + .header(AUTHORIZATION, String.format(BEARER_PLACEHOLDER, credentials.refreshAccessToken().getTokenValue())) + .GET() + .build(); + log.debug("GET Request: {}", getRequest); + + HttpResponse<String> getResponse = httpClient.send(getRequest, HttpResponse.BodyHandlers.ofString()); + log.debug("GET Response: {}", getResponse); + if (getResponse.statusCode() != 200 && getResponse.statusCode() != 404) { + logIfWarnEnabled(() -> log.warn("Received {} status code when creating entity but 200 or 404 were expected: {}", getResponse.statusCode(), getResponse.body())); + throw new GoogleWalletException("Cannot create Wallet class. Response status: " + getResponse.statusCode()); + } + + if (getResponse.statusCode() == 404 || overwritePreviousClassesAndEvents) { + HttpRequest.Builder builder = HttpRequest.newBuilder() + .header(AUTHORIZATION, String.format(BEARER_PLACEHOLDER, credentials.refreshAccessToken().getTokenValue())); + if (getResponse.statusCode() == 404) { + builder = builder + .uri(URI.create(uri)) + .POST(HttpRequest.BodyPublishers.ofString(entity.build(objectMapper))); + } else { + builder = builder + .uri(uriWithId) + .PUT(HttpRequest.BodyPublishers.ofString(entity.build(objectMapper))); + } + HttpRequest postOrPutRequest = builder.build(); + log.debug("POST or PUT Request: {}", postOrPutRequest); + HttpResponse<String> postOrPutResponse = httpClient.send(postOrPutRequest, HttpResponse.BodyHandlers.ofString()); + log.debug("POST or PUT Response: {}", postOrPutResponse); + if (postOrPutResponse.statusCode() != 200) { + logIfWarnEnabled(() -> log.warn("Received {} status code when creating entity: {}", postOrPutResponse.statusCode(), postOrPutResponse.body())); + throw new GoogleWalletException("Cannot create wallet. Response status: " + postOrPutResponse.statusCode()); + } + } + return entity.getId(); + } catch (IOException e) { + throw new GoogleWalletException("Error while communicating with the Google Wallet API", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new GoogleWalletException("Error while communicating with the Google Wallet API", e); + } + } + + private String walletIdPrefix() { + var profile = Stream.of(PROFILE_DEMO, PROFILE_DEV, PROFILE_LIVE) + .filter(p -> environment.acceptsProfiles(Profiles.of(p))) + .findFirst() + .orElseThrow(() -> new NoSuchElementException("No suitable Spring Profile found to create a Wallet ID prefix for classes and objects. Must have one of PROFILE_DEMO, PROFILE_DEV, or PROFILE_LIVE")); + if (profile.equals(PROFILE_LIVE)) { + return "live"; + } + return profile; + } + + private void logIfWarnEnabled(ConditionalLogger logger) { + if (log.isWarnEnabled()) { + logger.log(); + } + } + + @FunctionalInterface + private interface ConditionalLogger { + void log(); + } + +} diff --git a/src/main/java/alfio/manager/wallet/WalletEntity.java b/src/main/java/alfio/manager/wallet/WalletEntity.java new file mode 100644 index 0000000000..54be6e4567 --- /dev/null +++ b/src/main/java/alfio/manager/wallet/WalletEntity.java @@ -0,0 +1,27 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public interface WalletEntity { + + public String getId(); + + public String build(ObjectMapper mapper); + +} diff --git a/src/main/java/alfio/model/AdditionalService.java b/src/main/java/alfio/model/AdditionalService.java index fa95ebe8ea..f4f1fe7198 100644 --- a/src/main/java/alfio/model/AdditionalService.java +++ b/src/main/java/alfio/model/AdditionalService.java @@ -147,17 +147,12 @@ public String getChecksum() { } public static PriceContainer.VatStatus getVatStatus(VatType vatType, PriceContainer.VatStatus eventVatStatus) { - switch (vatType) { - case INHERITED: - return eventVatStatus; - case NONE: - return PriceContainer.VatStatus.NONE; - case CUSTOM_EXCLUDED: - return PriceContainer.VatStatus.NOT_INCLUDED; - case CUSTOM_INCLUDED: - return PriceContainer.VatStatus.INCLUDED; - default: - return PriceContainer.VatStatus.NOT_INCLUDED; - } + return switch (vatType) { + case INHERITED -> eventVatStatus; + case NONE -> PriceContainer.VatStatus.NONE; + case CUSTOM_EXCLUDED -> PriceContainer.VatStatus.NOT_INCLUDED; + case CUSTOM_INCLUDED -> PriceContainer.VatStatus.INCLUDED; + default -> PriceContainer.VatStatus.NOT_INCLUDED; + }; } } diff --git a/src/main/java/alfio/model/AdditionalServiceItem.java b/src/main/java/alfio/model/AdditionalServiceItem.java index c556a151f4..4f587a11de 100644 --- a/src/main/java/alfio/model/AdditionalServiceItem.java +++ b/src/main/java/alfio/model/AdditionalServiceItem.java @@ -24,8 +24,13 @@ @Getter public class AdditionalServiceItem { + // are considered "Confirmed": ACQUIRED, CHECKED_IN and TO_BE_PAID public enum AdditionalServiceItemStatus { - FREE, PENDING, TO_BE_PAID, ACQUIRED, CANCELLED, CHECKED_IN, EXPIRED, INVALIDATED, RELEASED + FREE, // <- unused + PENDING, TO_BE_PAID, ACQUIRED, CANCELLED, + CHECKED_IN, // <- unused + EXPIRED, + INVALIDATED, RELEASED // <- both unused } private final int id; diff --git a/src/main/java/alfio/model/AdditionalServiceItemExport.java b/src/main/java/alfio/model/AdditionalServiceItemExport.java index aa35631554..3ec4005948 100644 --- a/src/main/java/alfio/model/AdditionalServiceItemExport.java +++ b/src/main/java/alfio/model/AdditionalServiceItemExport.java @@ -32,6 +32,9 @@ public class AdditionalServiceItemExport { private final String emailAddress; private final String additionalServiceTitle; private final AdditionalService.AdditionalServiceType additionalServiceType; + + private final AdditionalServiceItem.AdditionalServiceItemStatus additionalServiceItemStatus; + private final Integer finalPriceCts; private final String currencyCode; private final Integer vatCts; @@ -49,7 +52,8 @@ public AdditionalServiceItemExport(@Column("ai_uuid") String uuid, @Column("ai_final_price_cts") Integer finalPriceCts, @Column("ai_currency_code") String currencyCode, @Column("ai_vat_cts") Integer vatCts, - @Column("ai_discount_cts") Integer discountCts) { + @Column("ai_discount_cts") Integer discountCts, + @Column("ai_status") AdditionalServiceItem.AdditionalServiceItemStatus additionalServiceItemStatus) { this.uuid = uuid; this.utcCreation = utcCreation; this.utcLastModified = utcLastModified; @@ -63,5 +67,6 @@ public AdditionalServiceItemExport(@Column("ai_uuid") String uuid, this.currencyCode = currencyCode; this.vatCts = vatCts; this.discountCts = discountCts; + this.additionalServiceItemStatus = additionalServiceItemStatus; } } diff --git a/src/main/java/alfio/model/AdditionalServiceText.java b/src/main/java/alfio/model/AdditionalServiceText.java index e065c45909..6ba733676b 100644 --- a/src/main/java/alfio/model/AdditionalServiceText.java +++ b/src/main/java/alfio/model/AdditionalServiceText.java @@ -17,31 +17,14 @@ package alfio.model; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class AdditionalServiceText { +public record AdditionalServiceText(@Column("id") int id, + @Column("additional_service_id_fk") Integer additionalServiceId, + @Column("locale") String locale, + @Column("type") TextType type, + @Column("value") String value) { public enum TextType { TITLE, DESCRIPTION } - - private final int id; - private final Integer additionalServiceId; - private final String locale; - private final TextType type; - private final String value; - - public AdditionalServiceText(@Column("id") int id, - @Column("additional_service_id_fk") Integer additionalServiceId, - @Column("locale") String locale, - @Column("type") TextType type, - @Column("value") String value) { - this.id = id; - this.additionalServiceId = additionalServiceId; - this.locale = locale; - this.type = type; - this.value = value; - } - } diff --git a/src/main/java/alfio/model/AdminReservationRequestStats.java b/src/main/java/alfio/model/AdminReservationRequestStats.java index 4b9b7c985c..4e85e13fbe 100644 --- a/src/main/java/alfio/model/AdminReservationRequestStats.java +++ b/src/main/java/alfio/model/AdminReservationRequestStats.java @@ -17,30 +17,11 @@ package alfio.model; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class AdminReservationRequestStats { - - private final String requestId; - private final long userId; - private final long eventId; - private final int countSuccess; - private final int countPending; - private final int countError; - - - public AdminReservationRequestStats(@Column("request_id") String requestId, - @Column("user_id") long userId, - @Column("event_id") long eventId, - @Column("count_success") int countSuccess, - @Column("count_pending") int countPending, - @Column("count_error") int countError) { - this.requestId = requestId; - this.userId = userId; - this.eventId = eventId; - this.countSuccess = countSuccess; - this.countPending = countPending; - this.countError = countError; - } +public record AdminReservationRequestStats(@Column("request_id") String requestId, + @Column("user_id") long userId, + @Column("event_id") long eventId, + @Column("count_success") int countSuccess, + @Column("count_pending") int countPending, + @Column("count_error") int countError) { } diff --git a/src/main/java/alfio/model/AllocationStatus.java b/src/main/java/alfio/model/AllocationStatus.java index bfdd9742a1..f2547ea7b4 100644 --- a/src/main/java/alfio/model/AllocationStatus.java +++ b/src/main/java/alfio/model/AllocationStatus.java @@ -19,5 +19,5 @@ public enum AllocationStatus { FREE, PRE_RESERVED, PENDING, TO_BE_PAID, ACQUIRED, CANCELLED, CHECKED_IN, EXPIRED, - INVALIDATED, RELEASED; + INVALIDATED, RELEASED } diff --git a/src/main/java/alfio/model/Audit.java b/src/main/java/alfio/model/Audit.java index 39dc074a0d..bcdf4b3c85 100644 --- a/src/main/java/alfio/model/Audit.java +++ b/src/main/java/alfio/model/Audit.java @@ -63,6 +63,7 @@ public enum EventType { VAT_VALIDATION_SUCCESSFUL, VAT_FORMAL_VALIDATION_SUCCESSFUL, VAT_VALIDATION_SKIPPED, + VAT_CUSTOM_CONFIGURATION_APPLIED, GROUP_MEMBER_ACQUIRED, CREDIT_NOTE_ISSUED, BILLING_DATA_UPDATED, @@ -102,7 +103,8 @@ public Audit(@Column("reservation_id") String reservationId, @Column("event_type this.eventTime = eventTime; this.entityType = entityType; this.entityId = entityId; - this.modifications = modifications == null ? null : Json.fromJson(modifications, new TypeReference<List<Map<String, Object>>>() {}); + this.modifications = modifications == null ? null : Json.fromJson(modifications, new TypeReference<>() { + }); this.username = username; this.firstName = firstName; this.lastName = lastName; diff --git a/src/main/java/alfio/model/BookedAdditionalService.java b/src/main/java/alfio/model/BookedAdditionalService.java index f58414dffa..6f3e26e5e0 100644 --- a/src/main/java/alfio/model/BookedAdditionalService.java +++ b/src/main/java/alfio/model/BookedAdditionalService.java @@ -17,20 +17,8 @@ package alfio.model; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class BookedAdditionalService { - - private final String additionalServiceName; - private final int additionalServiceId; - private final int count; - - public BookedAdditionalService(@Column("as_name") String additionalServiceName, - @Column("as_id") int additionalServiceId, - @Column("qty") int count) { - this.additionalServiceName = additionalServiceName; - this.additionalServiceId = additionalServiceId; - this.count = count; - } +public record BookedAdditionalService(@Column("as_name") String additionalServiceName, + @Column("as_id") int additionalServiceId, + @Column("qty") int count) { } diff --git a/src/main/java/alfio/model/Configurable.java b/src/main/java/alfio/model/Configurable.java index e6a5186e4a..5347b741e4 100644 --- a/src/main/java/alfio/model/Configurable.java +++ b/src/main/java/alfio/model/Configurable.java @@ -26,4 +26,6 @@ public interface Configurable { @JsonIgnore ConfigurationLevel getConfigurationLevel(); + + int getOrganizationId(); } diff --git a/src/main/java/alfio/model/ContentLanguage.java b/src/main/java/alfio/model/ContentLanguage.java index 4e5324c818..b405557420 100644 --- a/src/main/java/alfio/model/ContentLanguage.java +++ b/src/main/java/alfio/model/ContentLanguage.java @@ -17,13 +17,12 @@ package alfio.model; import alfio.util.LocaleUtil; -import lombok.EqualsAndHashCode; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.stream.Collectors; -@EqualsAndHashCode public class ContentLanguage { public static final int ENGLISH_IDENTIFIER = 0b00010; @@ -75,4 +74,17 @@ public Locale getLocale() { public int getValue() { return value; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ContentLanguage that = (ContentLanguage) o; + return value == that.value && Objects.equals(locale, that.locale) && Objects.equals(displayLocale, that.displayLocale); + } + + @Override + public int hashCode() { + return Objects.hash(locale, value, displayLocale); + } } diff --git a/src/main/java/alfio/model/DetailedScanData.java b/src/main/java/alfio/model/DetailedScanData.java index 339134ed17..0653d741fa 100644 --- a/src/main/java/alfio/model/DetailedScanData.java +++ b/src/main/java/alfio/model/DetailedScanData.java @@ -60,12 +60,13 @@ public DetailedScanData(@Column("t_id") int ticketId, @Column("s_event_id") int scanEventId, @Column("s_ticket_id") int scanTicketId, @Column("s_notes") String notes, - @Column("s_lead_status") SponsorScan.LeadStatus leadStatus) { + @Column("s_lead_status") SponsorScan.LeadStatus leadStatus, + @Column("s_operator") String operator) { this.ticket = new Ticket(ticketId, ticketUuid, ticketCreation, ticketCategoryId, ticketStatus, ticketEventId, ticketsReservationId, ticketFullName, ticketFirstName, ticketLastName, ticketEmail, ticketLockedAssignment, ticketUserLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference, currencyCode, ticketTags, ticketSubscriptionId, ticketVatStatus); - this.sponsorScan = new SponsorScan(scanUserId, scanTimestamp, scanEventId, scanTicketId, notes, leadStatus); + this.sponsorScan = new SponsorScan(scanUserId, scanTimestamp, scanEventId, scanTicketId, notes, leadStatus, operator); } } diff --git a/src/main/java/alfio/model/EmailMessage.java b/src/main/java/alfio/model/EmailMessage.java index 3601ae3cdb..1e3e21284e 100644 --- a/src/main/java/alfio/model/EmailMessage.java +++ b/src/main/java/alfio/model/EmailMessage.java @@ -106,13 +106,12 @@ public int compareTo(EmailMessage o) { @Override public boolean equals(Object obj) { - if(!(obj instanceof EmailMessage)) { + if(!(obj instanceof EmailMessage other)) { return false; } if(obj == this) { return true; } - EmailMessage other = (EmailMessage)obj; return new EqualsBuilder() .append(eventId, other.eventId) .append(subscriptionDescriptorId, other.subscriptionDescriptorId) diff --git a/src/main/java/alfio/model/EntityIdAndMetadata.java b/src/main/java/alfio/model/EntityIdAndMetadata.java index 7cba8e1b7d..cf41a4b3e9 100644 --- a/src/main/java/alfio/model/EntityIdAndMetadata.java +++ b/src/main/java/alfio/model/EntityIdAndMetadata.java @@ -19,16 +19,7 @@ import alfio.model.metadata.AlfioMetadata; import alfio.model.support.JSONData; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class EntityIdAndMetadata { - private final Integer id; - private final AlfioMetadata metadata; - - public EntityIdAndMetadata(@Column("id") Integer id, - @Column("metadata") @JSONData AlfioMetadata metadata) { - this.id = id; - this.metadata = metadata; - } +public record EntityIdAndMetadata(@Column("id") Integer id, + @Column("metadata") @JSONData AlfioMetadata metadata) { } diff --git a/src/main/java/alfio/model/Event.java b/src/main/java/alfio/model/Event.java index 2f8c464af5..251df828cb 100644 --- a/src/main/java/alfio/model/Event.java +++ b/src/main/java/alfio/model/Event.java @@ -18,11 +18,11 @@ import alfio.model.transaction.PaymentProxy; import alfio.util.ClockProvider; +import alfio.util.EventUtil; import alfio.util.MonetaryUtil; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.flywaydb.core.api.MigrationVersion; @@ -37,7 +37,6 @@ import static java.time.temporal.ChronoField.OFFSET_SECONDS; @Getter -@Log4j2 public class Event extends EventAndOrganizationId implements EventHiddenFieldContainer, EventCheckInInfo, PurchaseContext { private static final String VERSION_FOR_FIRST_AND_LAST_NAME = "15.1.8.8"; @@ -254,6 +253,10 @@ public boolean mustUseFirstAndLastName() { return mustUseFirstAndLastName(this); } + public boolean supportsQRCodeCaseInsensitive() { + return EventUtil.supportsCaseInsensitiveQRCode(version); + } + private static boolean mustUseFirstAndLastName(Event event) { return event.getVersion() != null diff --git a/src/main/java/alfio/model/EventCheckInInfo.java b/src/main/java/alfio/model/EventCheckInInfo.java index 4c457386b4..27385c50ba 100644 --- a/src/main/java/alfio/model/EventCheckInInfo.java +++ b/src/main/java/alfio/model/EventCheckInInfo.java @@ -16,14 +16,21 @@ */ package alfio.model; +import com.fasterxml.jackson.annotation.JsonIgnore; + import java.time.ZonedDateTime; public interface EventCheckInInfo extends TimeZoneInfo { + String VERSION_FOR_CODE_CASE_INSENSITIVE = "205.2.0.0.50"; + int getId(); String getPrivateKey(); ZonedDateTime getBegin(); ZonedDateTime getEnd(); Event.EventFormat getFormat(); + @JsonIgnore + boolean supportsQRCodeCaseInsensitive(); + } diff --git a/src/main/java/alfio/model/EventDescription.java b/src/main/java/alfio/model/EventDescription.java index a6bdaa2516..226a09a203 100644 --- a/src/main/java/alfio/model/EventDescription.java +++ b/src/main/java/alfio/model/EventDescription.java @@ -17,38 +17,17 @@ package alfio.model; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class EventDescription { + +public record EventDescription(@Column("event_id_fk") Integer eventId, @Column("locale") String locale, + @Column("type") EventDescriptionType eventDescriptionType, + @Column("description") String description) { // public enum EventDescriptionType { DESCRIPTION } - private final Integer eventId; - private final String locale; - private final EventDescriptionType eventDescriptionType; - private final String description; - - public EventDescription(@Column("event_id_fk") Integer eventId, @Column("locale") String locale, - @Column("type") EventDescriptionType eventDescriptionType, - @Column("description") String description) { - this.eventId = eventId; - this.locale = locale; - this.eventDescriptionType = eventDescriptionType; - this.description = description; - } - - @Getter - public static class LocaleDescription { - private final String locale; - private final String description; - - public LocaleDescription(@Column("locale") String locale, @Column("description") String description) { - this.locale = locale; - this.description = description; - } + public record LocaleDescription(@Column("locale") String locale, @Column("description") String description) { } } diff --git a/src/main/java/alfio/model/EventWithAdditionalInfo.java b/src/main/java/alfio/model/EventWithAdditionalInfo.java index 0b979b995f..6eec3e4f1d 100644 --- a/src/main/java/alfio/model/EventWithAdditionalInfo.java +++ b/src/main/java/alfio/model/EventWithAdditionalInfo.java @@ -16,18 +16,15 @@ */ package alfio.model; -import alfio.manager.support.extension.ExtensionCapability; import alfio.model.metadata.AlfioMetadata; import alfio.model.modification.StatisticsContainer; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Delegate; import java.math.BigDecimal; import java.util.*; -@AllArgsConstructor @Getter public class EventWithAdditionalInfo implements StatisticsContainer, PriceContainer { @@ -49,6 +46,24 @@ public class EventWithAdditionalInfo implements StatisticsContainer, PriceContai private final Set<ExtensionCapabilitySummary> supportedCapabilities; + public EventWithAdditionalInfo(Event event, + List<TicketCategoryWithAdditionalInfo> ticketCategories, + EventStatistic eventStatistic, + Map<String, String> description, + BigDecimal grossIncome, + AlfioMetadata metadata, + List<UUID> linkedSubscriptions, + Set<ExtensionCapabilitySummary> supportedCapabilities) { + this.event = event; + this.ticketCategories = ticketCategories; + this.eventStatistic = eventStatistic; + this.description = description; + this.grossIncome = grossIncome; + this.metadata = metadata; + this.linkedSubscriptions = linkedSubscriptions; + this.supportedCapabilities = supportedCapabilities; + } + @JsonIgnore public Event getEvent() { return event; diff --git a/src/main/java/alfio/model/ExtensionSupport.java b/src/main/java/alfio/model/ExtensionSupport.java index 507f6a4da5..ba15f637bf 100644 --- a/src/main/java/alfio/model/ExtensionSupport.java +++ b/src/main/java/alfio/model/ExtensionSupport.java @@ -19,7 +19,8 @@ import alfio.extension.ExtensionMetadata; import alfio.model.support.JSONData; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @Getter @@ -143,11 +144,17 @@ public String getConfigurationPathLevel() { } } - @AllArgsConstructor + @Getter public static class ExtensionMetadataValue { private final int id; private final String value; + + @JsonCreator + public ExtensionMetadataValue(@JsonProperty("id") int id, @JsonProperty("value") String value) { + this.id = id; + this.value = value; + } } diff --git a/src/main/java/alfio/model/OrderSummary.java b/src/main/java/alfio/model/OrderSummary.java index d171adf315..12d58dafcf 100644 --- a/src/main/java/alfio/model/OrderSummary.java +++ b/src/main/java/alfio/model/OrderSummary.java @@ -19,9 +19,12 @@ import alfio.util.MonetaryUtil; import lombok.Data; +import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; +import static alfio.util.MonetaryUtil.*; + @Data public class OrderSummary { private final TotalPrice originalTotalPrice; @@ -54,8 +57,8 @@ public boolean getNotYetPaid() { } public int getTicketAmount() { - return summary.stream().filter(s-> SummaryRow.SummaryType.TICKET == s.getType()) - .mapToInt(SummaryRow::getAmount) + return summary.stream().filter(s-> SummaryRow.SummaryType.TICKET == s.type()) + .mapToInt(SummaryRow::amount) .sum(); } @@ -63,7 +66,7 @@ public List<SummaryRow> getSummary() { //filter out the promotions code that have been inserted in the order but not used return summary.stream() - .filter(s-> !(s.isDiscount() && s.getAmount() == 0)) + .filter(s-> !(s.isDiscount() && s.amount() == 0)) .collect(Collectors.toList()); } @@ -87,18 +90,29 @@ public String getTotalNetPrice() { if(free) { return null; } - return MonetaryUtil.formatCents(originalTotalPrice.getPriceWithVAT() - originalTotalPrice.getVAT(), originalTotalPrice.getCurrencyCode());// FIXME can be null + return MonetaryUtil.formatCents(originalTotalPrice.priceWithVAT() - originalTotalPrice.VAT(), originalTotalPrice.currencyCode());// FIXME can be null } public int getPriceInCents() { - return originalTotalPrice.getPriceWithVAT(); + return originalTotalPrice.priceWithVAT(); } public String getDescriptionForPayment() { - return summary.stream().filter(r -> !r.isDiscount()).map(SummaryRow::getDescriptionForPayment).collect(Collectors.joining(", ")); + return summary.stream().filter(r -> !r.isDiscount() && !r.getTaxDetail()) + .map(SummaryRow::getDescriptionForPayment).collect(Collectors.joining(", ")); } public boolean getDisplaySplitPaymentNote() { return !free && (vatStatus == PriceContainer.VatStatus.INCLUDED_NOT_CHARGED || vatStatus == PriceContainer.VatStatus.NOT_INCLUDED_NOT_CHARGED); } + + public String getPriceBeforeTaxes() { + String currencyCode = originalTotalPrice.currencyCode(); + if(PriceContainer.VatStatus.isVatIncluded(vatStatus)) { + var vat = vatStatus.extractVat(centsToUnit(originalTotalPrice.priceWithVAT(), currencyCode), new BigDecimal(vatPercentage)); + return formatUnit(new BigDecimal(getTotalPrice()).subtract(vat), currencyCode); + } else { + return formatCents(originalTotalPrice.priceWithVAT() - originalTotalPrice.VAT(), currencyCode); + } + } } diff --git a/src/main/java/alfio/model/PaymentInformation.java b/src/main/java/alfio/model/PaymentInformation.java index c400564049..10ddf7aea7 100644 --- a/src/main/java/alfio/model/PaymentInformation.java +++ b/src/main/java/alfio/model/PaymentInformation.java @@ -16,18 +16,23 @@ */ package alfio.model; -import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.commons.lang3.StringUtils; @Getter -@AllArgsConstructor public class PaymentInformation { private final String paidAmount; private final String refundedAmount; private final String fee; private final String platformFee; + public PaymentInformation(String paidAmount, String refundedAmount, String fee, String platformFee) { + this.paidAmount = paidAmount; + this.refundedAmount = refundedAmount; + this.fee = fee; + this.platformFee = platformFee; + } + public boolean isFullyRefunded() { return StringUtils.equals(paidAmount, refundedAmount); } diff --git a/src/main/java/alfio/model/PriceContainer.java b/src/main/java/alfio/model/PriceContainer.java index 129ebf5df4..de23c34043 100644 --- a/src/main/java/alfio/model/PriceContainer.java +++ b/src/main/java/alfio/model/PriceContainer.java @@ -39,6 +39,9 @@ enum VatStatus { NOT_INCLUDED(notIncludedVatCalculator, UnaryOperator.identity()), INCLUDED_EXEMPT(includedVatExtractor, BigDecimal::negate), NOT_INCLUDED_EXEMPT((price, vatPercentage) -> BigDecimal.ZERO, UnaryOperator.identity()), + // tax exemption was granted by custom rules (extension CUSTOM_TAX_POLICY_APPLICATION) + CUSTOM_INCLUDED_EXEMPT(includedVatExtractor, BigDecimal::negate), + CUSTOM_NOT_INCLUDED_EXEMPT((price, vatPercentage) -> BigDecimal.ZERO, UnaryOperator.identity()), // The following two are dedicated for handling italian-specific cases, "split payment" // VAT has to be shown on the invoice, but not charged INCLUDED_NOT_CHARGED(includedVatExtractor, UnaryOperator.identity()), @@ -62,11 +65,19 @@ public BigDecimal extractRawVAT(BigDecimal price, BigDecimal vatPercentage) { } public static boolean isVatExempt(VatStatus vatStatus) { - return vatStatus == INCLUDED_EXEMPT || vatStatus == NOT_INCLUDED_EXEMPT; + return vatStatus == INCLUDED_EXEMPT || vatStatus == NOT_INCLUDED_EXEMPT + || vatStatus == CUSTOM_INCLUDED_EXEMPT || vatStatus == CUSTOM_NOT_INCLUDED_EXEMPT; } public static boolean isVatIncluded(VatStatus vatStatus) { - return vatStatus == INCLUDED || vatStatus == INCLUDED_EXEMPT; + return vatStatus == INCLUDED || vatStatus == INCLUDED_EXEMPT || vatStatus == CUSTOM_INCLUDED_EXEMPT; + } + + public static VatStatus forceExempt(VatStatus original) { + if (isVatIncluded(original)) { + return INCLUDED_EXEMPT; + } + return NOT_INCLUDED_EXEMPT; } } @@ -122,15 +133,11 @@ default BigDecimal getFinalPrice() { } final BigDecimal price = centsToUnit(getSrcPriceCts(), getCurrencyCode()); BigDecimal discountedPrice = price.subtract(getAppliedDiscount()); - switch(vatStatus) { - case INCLUDED: - case NOT_INCLUDED_NOT_CHARGED: - return discountedPrice; - case INCLUDED_NOT_CHARGED: - return discountedPrice.subtract(getVAT()); - default: - return discountedPrice.add(getVAT()); - } + return switch (vatStatus) { + case INCLUDED, NOT_INCLUDED_NOT_CHARGED -> discountedPrice; + case INCLUDED_NOT_CHARGED -> discountedPrice.subtract(getVAT()); + default -> discountedPrice.add(getVAT()); + }; } /** @@ -157,8 +164,8 @@ default BigDecimal getVAT() { @JsonIgnore default BigDecimal getAppliedDiscount() { return getDiscount() - // do not take into account reservation-level discount - .filter(discount -> discount.getDiscountType() != PromoCodeDiscount.DiscountType.FIXED_AMOUNT_RESERVATION) + // do not take into account reservation-level discount or access codes + .filter(discount -> discount.getDiscountType() != PromoCodeDiscount.DiscountType.FIXED_AMOUNT_RESERVATION && discount.getCodeType() != PromoCodeDiscount.CodeType.ACCESS) .map(discount -> { String currencyCode = getCurrencyCode(); final BigDecimal price = centsToUnit(getSrcPriceCts(), currencyCode); @@ -176,9 +183,9 @@ default BigDecimal getAppliedDiscount() { default BigDecimal getNetPrice() { var vatStatus = getVatStatus(); var currencyCode = getCurrencyCode(); - if(vatStatus == VatStatus.NOT_INCLUDED_EXEMPT) { + if(vatStatus == VatStatus.NOT_INCLUDED_EXEMPT || vatStatus == VatStatus.CUSTOM_NOT_INCLUDED_EXEMPT) { return MonetaryUtil.centsToUnit(getSrcPriceCts(), currencyCode); - } else if(vatStatus == VatStatus.INCLUDED_EXEMPT) { + } else if(vatStatus == VatStatus.INCLUDED_EXEMPT || vatStatus == VatStatus.CUSTOM_INCLUDED_EXEMPT) { var rawVat = vatStatus.extractRawVAT(centsToUnit(getSrcPriceCts(), getCurrencyCode()), getVatPercentageOrZero()); return MonetaryUtil.centsToUnit(getSrcPriceCts(), currencyCode).add(rawVat); } else if(vatStatus == VatStatus.INCLUDED || vatStatus == VatStatus.INCLUDED_NOT_CHARGED) { diff --git a/src/main/java/alfio/model/PromoCodeDiscount.java b/src/main/java/alfio/model/PromoCodeDiscount.java index 6e89079c0b..18505bbcb1 100644 --- a/src/main/java/alfio/model/PromoCodeDiscount.java +++ b/src/main/java/alfio/model/PromoCodeDiscount.java @@ -22,6 +22,7 @@ import com.google.gson.reflect.TypeToken; import lombok.Getter; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -61,6 +62,7 @@ public static boolean isFixedAmount(DiscountType discountType) { private final String emailReference; private final CodeType codeType; private final Integer hiddenCategoryId; + private final String currencyCode; public PromoCodeDiscount(@Column("id")int id, @Column("promo_code") String promoCode, @@ -75,7 +77,8 @@ public PromoCodeDiscount(@Column("id")int id, @Column("description") String description, @Column("email_reference") String emailReference, @Column("code_type") CodeType codeType, - @Column("hidden_category_id") Integer hiddenCategoryId) { + @Column("hidden_category_id") Integer hiddenCategoryId, + @Column("currency_code") String currencyCode) { this.id = id; this.promoCode = promoCode; @@ -96,6 +99,7 @@ public PromoCodeDiscount(@Column("id")int id, this.emailReference = emailReference; this.codeType = codeType; this.hiddenCategoryId = hiddenCategoryId; + this.currencyCode = currencyCode; } public boolean isCurrentlyValid(ZoneId eventZoneId, ZonedDateTime now) { @@ -125,10 +129,18 @@ public boolean isDynamic() { return codeType == CodeType.DYNAMIC; } + public boolean hasCurrencyCode() { + return StringUtils.length(currencyCode) == 3; + } + public static String format(PromoCodeDiscount discount, String eventCurrencyCode) { if(discount.getDiscountType() == DiscountType.PERCENTAGE) { return Integer.toString(discount.getDiscountAmount()); } return MonetaryUtil.formatCents(discount.getDiscountAmount(), eventCurrencyCode); } + + public static boolean supportsCurrencyCode(CodeType codeType, DiscountType discountType) { + return codeType == CodeType.DISCOUNT && discountType != DiscountType.PERCENTAGE; + } } diff --git a/src/main/java/alfio/model/PromoCodeUsageResult.java b/src/main/java/alfio/model/PromoCodeUsageResult.java new file mode 100644 index 0000000000..db052d5b01 --- /dev/null +++ b/src/main/java/alfio/model/PromoCodeUsageResult.java @@ -0,0 +1,59 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model; + +import alfio.model.support.EventBasicInfo; +import alfio.model.support.ReservationInfo; +import alfio.util.Json; +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class PromoCodeUsageResult { + + private final String promoCode; + private final EventBasicInfo event; + private final List<ReservationInfo> reservations; + + public PromoCodeUsageResult(@Column("promo_code") String promoCode, + @Column("event_short_name") String eventShortName, + @Column("event_display_name") String eventDisplayName, + @Column("reservations") String reservationsJson) { + this.promoCode = promoCode; + this.event = new EventBasicInfo(eventShortName, eventDisplayName); + List<ReservationInfo> parsed = Json.fromJson(reservationsJson, new TypeReference<>() {}); + this.reservations = parsed.stream() + .sorted(Comparator.comparing(ReservationInfo::getConfirmationTimestamp)) + .collect(Collectors.toList()); + } + + public String getPromoCode() { + return promoCode; + } + + public EventBasicInfo getEvent() { + return event; + } + + public List<ReservationInfo> getReservations() { + return reservations; + } + +} diff --git a/src/main/java/alfio/model/PurchaseContext.java b/src/main/java/alfio/model/PurchaseContext.java index cb818c17a8..7e6d6b4cc9 100644 --- a/src/main/java/alfio/model/PurchaseContext.java +++ b/src/main/java/alfio/model/PurchaseContext.java @@ -67,11 +67,11 @@ enum PurchaseContextType { } public static PurchaseContextType from(String purchaseContextType) { - switch (purchaseContextType) { - case "subscription": return subscription; - case "event": return event; - default: throw new IllegalStateException("Purchase type not supported:" + purchaseContextType); - } + return switch (purchaseContextType) { + case "subscription" -> subscription; + case "event" -> event; + default -> throw new IllegalStateException("Purchase type not supported:" + purchaseContextType); + }; } public String getUrlComponent() { diff --git a/src/main/java/alfio/model/ReservationMetadata.java b/src/main/java/alfio/model/ReservationMetadata.java new file mode 100644 index 0000000000..402faf3787 --- /dev/null +++ b/src/main/java/alfio/model/ReservationMetadata.java @@ -0,0 +1,70 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ReservationMetadata { + + private final boolean hideContactData; + private final boolean readyForConfirmation; + private final boolean finalized; + private final boolean hideConfirmationButtons; + private final boolean lockEmailEdit; + + @JsonCreator + public ReservationMetadata(@JsonProperty("hideContactData") Boolean hideContactData, + @JsonProperty("readyForConfirmation") Boolean readyForConfirmation, + @JsonProperty("finalized") Boolean finalized, + @JsonProperty("hideConfirmationButtons") Boolean hideConfirmationButtons, + @JsonProperty("lockEmailEdit") Boolean lockEmailEdit) { + this.hideContactData = Boolean.TRUE.equals(hideContactData); + this.readyForConfirmation = Boolean.TRUE.equals(readyForConfirmation); + this.finalized = Boolean.TRUE.equals(finalized); + this.hideConfirmationButtons = Boolean.TRUE.equals(hideConfirmationButtons); + this.lockEmailEdit = Boolean.TRUE.equals(lockEmailEdit); + } + + public boolean isHideContactData() { + return hideContactData; + } + + public boolean isFinalized() { + return finalized; + } + + public boolean isReadyForConfirmation() { + return readyForConfirmation; + } + + public boolean isHideConfirmationButtons() { + return hideConfirmationButtons; + } + + public boolean isLockEmailEdit() { + return lockEmailEdit; + } + + public ReservationMetadata withFinalized(boolean newValue) { + return new ReservationMetadata(hideContactData, readyForConfirmation, newValue, hideConfirmationButtons, lockEmailEdit); + } + + public ReservationMetadata withReadyForConfirmation(boolean newValue) { + return new ReservationMetadata(hideContactData, newValue, finalized, hideConfirmationButtons, lockEmailEdit); + } +} diff --git a/src/main/java/alfio/model/ReservationPaymentDetail.java b/src/main/java/alfio/model/ReservationPaymentDetail.java new file mode 100644 index 0000000000..770e8e616d --- /dev/null +++ b/src/main/java/alfio/model/ReservationPaymentDetail.java @@ -0,0 +1,97 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model; + +import alfio.util.MonetaryUtil; +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; + +import java.time.ZonedDateTime; + +public class ReservationPaymentDetail { + private final String id; + private final String firstName; + private final String lastName; + private final String email; + private final String paymentMethod; + private final String paidAmount; + private final String currencyCode; + private final String transactionTimestamp; + private final String transactionNotes; + private final String invoiceNumber; + + public ReservationPaymentDetail(@Column("tr_id") String id, + @Column("tr_first_name") String firstName, + @Column("tr_last_name") String lastName, + @Column("tr_email_address") String email, + @Column("tr_payment_method") String paymentMethod, + @Column("bt_price_cts") Integer paidAmount, + @Column("bt_currency") String currencyCode, + @Column("bt_t_timestamp") ZonedDateTime transactionTimestamp, + @Column("bt_notes") String transactionNotes, + @Column("tr_invoice_number") String invoiceNumber) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.paymentMethod = paymentMethod; + this.paidAmount = MonetaryUtil.formatCents(paidAmount, currencyCode); + this.currencyCode = currencyCode; + this.transactionTimestamp = transactionTimestamp.toString(); + this.transactionNotes = transactionNotes; + this.invoiceNumber = invoiceNumber; + } + + public String getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getEmail() { + return email; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public String getPaidAmount() { + return paidAmount; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public String getTransactionTimestamp() { + return transactionTimestamp; + } + + public String getTransactionNotes() { + return transactionNotes; + } + + public String getInvoiceNumber() { + return invoiceNumber; + } +} diff --git a/src/main/java/alfio/model/ReservationWithPurchaseContext.java b/src/main/java/alfio/model/ReservationWithPurchaseContext.java index a471719fb6..4f84ed1a53 100644 --- a/src/main/java/alfio/model/ReservationWithPurchaseContext.java +++ b/src/main/java/alfio/model/ReservationWithPurchaseContext.java @@ -20,8 +20,9 @@ import alfio.model.transaction.PaymentProxy; import alfio.util.Json; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; -import lombok.AllArgsConstructor; import lombok.Getter; import java.math.BigDecimal; @@ -123,12 +124,23 @@ public BigDecimal getAppliedDiscount() { return centsToUnit(discountCts, currencyCode); } - @AllArgsConstructor + @Getter public static class PurchaseContextItem { private final String id; private final String firstName; private final String lastName; private final Map<String, String> type; + + @JsonCreator + public PurchaseContextItem(@JsonProperty("id") String id, + @JsonProperty("firstName") String firstName, + @JsonProperty("lastName") String lastName, + @JsonProperty("type") Map<String, String> type) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.type = type; + } } } diff --git a/src/main/java/alfio/model/ReservationsByEvent.java b/src/main/java/alfio/model/ReservationsByEvent.java new file mode 100644 index 0000000000..30380d1e36 --- /dev/null +++ b/src/main/java/alfio/model/ReservationsByEvent.java @@ -0,0 +1,58 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model; + +import alfio.model.support.ReservationInfo; +import alfio.util.Json; +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.List; + +public class ReservationsByEvent { + + private final int eventId; + private final String eventShortName; + private final String displayName; + private final List<ReservationInfo> reservations; + + public ReservationsByEvent(@Column("event_id") int eventId, + @Column("event_short_name") String eventShortName, + @Column("event_display_name") String displayName, + @Column("reservations") String reservations) { + this.eventId = eventId; + this.eventShortName = eventShortName; + this.displayName = displayName; + this.reservations = Json.fromJson(reservations, new TypeReference<>() {}); + } + + public int getEventId() { + return eventId; + } + + public String getEventShortName() { + return eventShortName; + } + + public String getDisplayName() { + return displayName; + } + + public List<ReservationInfo> getReservations() { + return reservations; + } +} diff --git a/src/main/java/alfio/model/RestrictedValueStats.java b/src/main/java/alfio/model/RestrictedValueStats.java index 2b674bd41d..ad4457169b 100644 --- a/src/main/java/alfio/model/RestrictedValueStats.java +++ b/src/main/java/alfio/model/RestrictedValueStats.java @@ -18,7 +18,6 @@ import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; import lombok.Data; -import lombok.Getter; @Data public class RestrictedValueStats { @@ -26,14 +25,7 @@ public class RestrictedValueStats { private final int count; private final int percentage; - @Getter - public static class RestrictedValueCount { - private final String name; - private final Integer count; - public RestrictedValueCount(@Column("name") String name, @Column("count") Integer count) { - this.name = name; - this.count = count; - } + public record RestrictedValueCount(@Column("name") String name, @Column("count") Integer count) { } } diff --git a/src/main/java/alfio/model/SponsorScan.java b/src/main/java/alfio/model/SponsorScan.java index 38426a3e49..1e069fd335 100644 --- a/src/main/java/alfio/model/SponsorScan.java +++ b/src/main/java/alfio/model/SponsorScan.java @@ -34,6 +34,7 @@ public enum LeadStatus { private final int ticketId; private final String notes; private final LeadStatus leadStatus; + private final String operator; public SponsorScan(@Column("user_id") int userId, @@ -41,12 +42,14 @@ public SponsorScan(@Column("user_id") int userId, @Column("event_id") int eventId, @Column("ticket_id") int ticketId, @Column("notes") String notes, - @Column("lead_status") LeadStatus leadStatus) { + @Column("lead_status") LeadStatus leadStatus, + @Column("operator") String operator) { this.userId = userId; this.timestamp = timestamp; this.eventId = eventId; this.ticketId = ticketId; this.notes = notes; this.leadStatus = leadStatus; + this.operator = operator; } } diff --git a/src/main/java/alfio/model/SummaryRow.java b/src/main/java/alfio/model/SummaryRow.java index d28d0c1107..49ff0f5690 100644 --- a/src/main/java/alfio/model/SummaryRow.java +++ b/src/main/java/alfio/model/SummaryRow.java @@ -16,26 +16,22 @@ */ package alfio.model; -import lombok.Data; - -@Data -public class SummaryRow { - private final String name; - private final String price; - private final String priceBeforeVat; - private final int amount; - private final String subTotal; - private final String subTotalBeforeVat; - private final int originalSubTotal; - private final SummaryType type; - private final String taxPercentage; - +public record SummaryRow(String name, + String price, + String priceBeforeVat, + int amount, + String subTotal, + String subTotalBeforeVat, + int originalSubTotal, + SummaryType type, + String taxPercentage, + PriceContainer.VatStatus vatStatus) { public enum SummaryType { TICKET, SUBSCRIPTION, PROMOTION_CODE, DYNAMIC_DISCOUNT, ADDITIONAL_SERVICE, APPLIED_SUBSCRIPTION, TAX_DETAIL } public String getDescriptionForPayment() { - if(name != null) { + if (name != null) { return amount + " x " + name; } return ""; diff --git a/src/main/java/alfio/model/Ticket.java b/src/main/java/alfio/model/Ticket.java index e02d9576da..caf9d7377a 100644 --- a/src/main/java/alfio/model/Ticket.java +++ b/src/main/java/alfio/model/Ticket.java @@ -18,6 +18,7 @@ import alfio.model.support.Array; import alfio.util.MonetaryUtil; +import alfio.util.checkin.NameNormalizer; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @@ -126,12 +127,12 @@ public boolean getLockedAssignment() { * @param eventKey * @return */ - public String ticketCode(String eventKey) { - return uuid + '/' + hmacTicketInfo(eventKey); + public String ticketCode(String eventKey, boolean caseInsensitive) { + return uuid + '/' + hmacTicketInfo(eventKey, caseInsensitive); } - public String hmacTicketInfo(String eventKey) { - return hmacSHA256Base64(eventKey, StringUtils.join(new String[]{ticketsReservationId , uuid, getFullName(), email}, '/')); + public String hmacTicketInfo(String eventKey, boolean caseInsensitive) { + return generateHmacTicketInfo(eventKey, caseInsensitive, getFullName(), email, ticketsReservationId, uuid); } public boolean hasBeenSold() { @@ -143,6 +144,16 @@ public boolean isCheckedIn() { return status == TicketStatus.CHECKED_IN; } + static String generateHmacTicketInfo(String eventKey, boolean caseInsensitive, String fullName, String email, String ticketsReservationId, String uuid) { + var attendeeName = fullName; + var attendeeEmail = email; + if (caseInsensitive) { + attendeeName = NameNormalizer.normalize(attendeeName); + attendeeEmail = email.toLowerCase(Locale.ROOT); + } + return hmacSHA256Base64(eventKey, StringUtils.join(new String[]{ticketsReservationId , uuid, attendeeName, attendeeEmail}, '/')); + } + public static String hmacSHA256Base64(String key, String code) { return Base64.getEncoder().encodeToString(new HmacUtils(HmacAlgorithms.HMAC_SHA_256, key).hmac(code)); } @@ -158,4 +169,31 @@ public String getFormattedFinalPrice() { public String getFormattedNetPrice() { return MonetaryUtil.formatCents(finalPriceCts - vatCts, currencyCode); } + + public Ticket withVatStatus(PriceContainer.VatStatus newVatStatus) { + return new Ticket( + id, + uuid, + creation, + categoryId, + status.name(), + eventId, + ticketsReservationId, + fullName, + firstName, + lastName, + email, + lockedAssignment, + userLanguage, + srcPriceCts, + finalPriceCts, + vatCts, + discountCts, + extReference, + currencyCode, + tags, + subscriptionId, + newVatStatus + ); + } } diff --git a/src/main/java/alfio/model/TicketCategoryStatisticView.java b/src/main/java/alfio/model/TicketCategoryStatisticView.java index f398818da0..0b8d60b6ea 100644 --- a/src/main/java/alfio/model/TicketCategoryStatisticView.java +++ b/src/main/java/alfio/model/TicketCategoryStatisticView.java @@ -16,9 +16,16 @@ */ package alfio.model; +import alfio.model.system.Configuration; +import alfio.util.Json; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.Getter; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Objects; + @Getter public class TicketCategoryStatisticView { @@ -36,6 +43,7 @@ public class TicketCategoryStatisticView { private final int stuckCount; private final boolean containsOrphanTickets; private final boolean containsStuckTickets; + private final List<Configuration> configuration; public TicketCategoryStatisticView(@Column("ticket_category_id") int id, @Column("max_tickets") int maxTickets, @@ -49,7 +57,8 @@ public TicketCategoryStatisticView(@Column("ticket_category_id") int id, @Column("released_count") int releasedCount, @Column("stuck_count") int stuckCount, @Column("is_containing_orphan_tickets") boolean containsOrphanTickets, - @Column("is_containing_stuck_tickets") boolean containsStuckTickets) { + @Column("is_containing_stuck_tickets") boolean containsStuckTickets, + @Column("category_configuration") String configurationJson) { this.id = id; this.maxTickets = maxTickets; this.bounded = bounded; @@ -63,9 +72,10 @@ public TicketCategoryStatisticView(@Column("ticket_category_id") int id, this.containsOrphanTickets = containsOrphanTickets; this.containsStuckTickets = containsStuckTickets; this.releasedTicketsCount = releasedCount; + this.configuration = Json.fromJson(Objects.requireNonNullElse(configurationJson, "[]"), new TypeReference<>() {}); } public static TicketCategoryStatisticView empty(int ticketCategoryId, int eventId) { - return new TicketCategoryStatisticView(ticketCategoryId, 0, false, false, eventId, 0, 0, 0, 0, 0, 0, false, false); + return new TicketCategoryStatisticView(ticketCategoryId, 0, false, false, eventId, 0, 0, 0, 0, 0, 0, false, false, null); } } diff --git a/src/main/java/alfio/model/TicketCategoryWithAdditionalInfo.java b/src/main/java/alfio/model/TicketCategoryWithAdditionalInfo.java index 2aa26216d0..549308077b 100644 --- a/src/main/java/alfio/model/TicketCategoryWithAdditionalInfo.java +++ b/src/main/java/alfio/model/TicketCategoryWithAdditionalInfo.java @@ -19,10 +19,10 @@ import alfio.model.metadata.AlfioMetadata; import alfio.model.modification.StatisticsContainer; import alfio.model.modification.TicketWithStatistic; +import alfio.model.system.Configuration; import alfio.util.ClockProvider; import alfio.util.MonetaryUtil; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Delegate; @@ -33,7 +33,6 @@ import java.util.Map; import java.util.Optional; -@AllArgsConstructor @Getter public class TicketCategoryWithAdditionalInfo implements StatisticsContainer, PriceContainer { @@ -57,6 +56,20 @@ public class TicketCategoryWithAdditionalInfo implements StatisticsContainer, Pr @Deprecated private final List<TicketWithStatistic> tickets = Collections.emptyList(); + public TicketCategoryWithAdditionalInfo(Event event, + TicketCategory ticketCategory, + TicketCategoryStatisticView ticketCategoryStatisticView, + Map<String, String> description, + List<SpecialPrice> tokenStatus, + AlfioMetadata metadata) { + this.event = event; + this.ticketCategory = ticketCategory; + this.ticketCategoryStatisticView = ticketCategoryStatisticView; + this.description = description; + this.tokenStatus = tokenStatus; + this.metadata = metadata; + } + @JsonIgnore public Event getEvent() { return event; @@ -170,4 +183,8 @@ public Optional<BigDecimal> getOptionalVatPercentage() { public VatStatus getVatStatus() { return event.getVatStatus(); } + + public List<Configuration> getConfiguration() { + return ticketCategoryStatisticView.getConfiguration(); + } } diff --git a/src/main/java/alfio/model/TicketFieldConfiguration.java b/src/main/java/alfio/model/TicketFieldConfiguration.java index d249d11fa6..869a3f38f2 100644 --- a/src/main/java/alfio/model/TicketFieldConfiguration.java +++ b/src/main/java/alfio/model/TicketFieldConfiguration.java @@ -97,6 +97,10 @@ public boolean isSelectField() { return "select".equals(type); } + public boolean isCheckboxField() { + return "checkbox".equals(type); + } + public int getCount() { if ("checkbox".equals(type) && this.restrictedValues != null) { return Math.max(this.restrictedValues.size(), 1); diff --git a/src/main/java/alfio/model/TicketFieldConfigurationDescriptionAndValue.java b/src/main/java/alfio/model/TicketFieldConfigurationDescriptionAndValue.java index 6e199d39c8..2678f17d79 100644 --- a/src/main/java/alfio/model/TicketFieldConfigurationDescriptionAndValue.java +++ b/src/main/java/alfio/model/TicketFieldConfigurationDescriptionAndValue.java @@ -18,21 +18,20 @@ import alfio.util.Json; import com.fasterxml.jackson.core.type.TypeReference; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Triple; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; -@AllArgsConstructor public class TicketFieldConfigurationDescriptionAndValue { @Delegate @@ -42,6 +41,24 @@ public class TicketFieldConfigurationDescriptionAndValue { private final int count; private final String value; + private static final List<String> TEXT_FIELD_TYPES = List.of( + "text", + "tel", + "textarea", + "vat:eu" + ); + private static final Pattern CHECKBOX_VALUES_PATTERN = Pattern.compile("\"(.*?)\",?"); + + public TicketFieldConfigurationDescriptionAndValue(TicketFieldConfiguration ticketFieldConfiguration, + TicketFieldDescription ticketFieldDescription, + int count, + String value) { + this.ticketFieldConfiguration = ticketFieldConfiguration; + this.ticketFieldDescription = ticketFieldDescription; + this.count = count; + this.value = value; + } + public String getTranslatedValue() { if(StringUtils.isBlank(value)) { return value; @@ -71,18 +88,38 @@ public List<TicketFieldValue> getFields() { } + private boolean isText() { + return TEXT_FIELD_TYPES.contains(getType()); + } + public String getValueDescription() { - if(isSelectField()) { - return getTranslatedRestrictedValue().stream() - .filter(t -> StringUtils.equals(t.getLeft(), value)) - .map(Triple::getMiddle) - .findFirst() - .orElse(""); - } else { + if(isText()) { return value; + } else if(isCheckboxField()) { + var matches = new ArrayList<String>(); + var matcher = CHECKBOX_VALUES_PATTERN.matcher(value); + while(matcher.find()) { + matches.add(matcher.group(1)); + } + var restrictedValues = getTranslatedRestrictedValue(); + return matches.stream() + .map(v -> findValueDescription(restrictedValues, v)) + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining(", ")); + } else { + return findValueDescription(getTranslatedRestrictedValue(), value); } } + private String findValueDescription(List<Triple<String, String, Boolean>> translateRestrictedValues, + String value) { + return translateRestrictedValues.stream() + .filter(t -> StringUtils.equals(t.getLeft(), value)) + .map(Triple::getMiddle) + .findFirst() + .orElse(""); + } + public String getValue() { return value; } @@ -101,13 +138,20 @@ private static boolean isFieldValueEnabled(TicketFieldConfiguration ticketFieldC || !ticketFieldConfiguration.getDisabledValues().contains(value); } - @RequiredArgsConstructor + @Getter public static class TicketFieldValue { private final int fieldIndex; private final int fieldCounter; private final String fieldValue; private final Boolean editable; + + public TicketFieldValue(int fieldIndex, int fieldCounter, String fieldValue, Boolean editable) { + this.fieldIndex = fieldIndex; + this.fieldCounter = fieldCounter; + this.fieldValue = fieldValue; + this.editable = editable; + } } } diff --git a/src/main/java/alfio/model/TicketInfo.java b/src/main/java/alfio/model/TicketInfo.java index 29ee8f536a..0209b5c51f 100644 --- a/src/main/java/alfio/model/TicketInfo.java +++ b/src/main/java/alfio/model/TicketInfo.java @@ -23,15 +23,21 @@ public class TicketInfo { private final int ticketId; + private final String ticketUuid; private final int ticketCategoryId; private final boolean ticketCategoryBounded; + private final PriceContainer.VatStatus taxPolicy; public TicketInfo(@Column("t_id") int id, + @Column("t_uuid") String ticketUuid, @Column("tc_id") int tcId, - @Column("tc_bounded") boolean bounded) { + @Column("tc_bounded") boolean bounded, + @Column("t_vat_status") PriceContainer.VatStatus taxPolicy) { this.ticketId = id; + this.ticketUuid = ticketUuid; this.ticketCategoryId = tcId; this.ticketCategoryBounded = bounded; + this.taxPolicy = taxPolicy; } } diff --git a/src/main/java/alfio/model/TicketReservation.java b/src/main/java/alfio/model/TicketReservation.java index ff98ba0e22..6b34d31e31 100644 --- a/src/main/java/alfio/model/TicketReservation.java +++ b/src/main/java/alfio/model/TicketReservation.java @@ -21,7 +21,6 @@ import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; -import lombok.With; import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; @@ -39,6 +38,14 @@ public enum TicketReservationStatus { WAITING_EXTERNAL_CONFIRMATION, OFFLINE_PAYMENT, DEFERRED_OFFLINE_PAYMENT, + /** + * Reservation is in the process of being finalized + */ + FINALIZING, + /** + * Special finalization status for OFFLINE payment + */ + OFFLINE_FINALIZING, COMPLETE, STUCK, CANCELLED, @@ -64,7 +71,6 @@ public enum TicketReservationStatus { private final String invoiceNumber; @JsonIgnore private final String invoiceModel; - @With private final PriceContainer.VatStatus vatStatus; private final String vatNr; private final String vatCountryCode; @@ -204,6 +210,44 @@ public String getPaidAmount() { return null; } + public TicketReservation withVatStatus(PriceContainer.VatStatus vatStatus) { + if (this.vatStatus == vatStatus) { + return this; + } + return new TicketReservation(this.id, + this.validity, + this.status, + this.fullName, + this.firstName, + this.lastName, + this.email, + this.billingAddress, + this.confirmationTimestamp, + this.latestReminder, + this.paymentMethod, + this.reminderSent, + this.promoCodeDiscountId, + this.automatic, + this.userLanguage, + this.directAssignmentRequested, + this.invoiceNumber, + this.invoiceModel, + vatStatus, + this.vatNr, + this.vatCountryCode, + this.invoiceRequested, + this.usedVatPercent, + this.vatIncluded, + this.creationTimestamp, + this.customerReference, + this.registrationTimestamp, + this.srcPriceCts, + this.finalPriceCts, + this.vatCts, + this.discountCts, + this.currencyCode); + } + @Override public Optional<BigDecimal> getOptionalVatPercentage() { return Optional.ofNullable(usedVatPercent); diff --git a/src/main/java/alfio/model/TicketReservationInvoicingAdditionalInfo.java b/src/main/java/alfio/model/TicketReservationInvoicingAdditionalInfo.java index 7e9e5aa246..13731006ce 100644 --- a/src/main/java/alfio/model/TicketReservationInvoicingAdditionalInfo.java +++ b/src/main/java/alfio/model/TicketReservationInvoicingAdditionalInfo.java @@ -16,15 +16,20 @@ */ package alfio.model; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @Getter -@AllArgsConstructor public class TicketReservationInvoicingAdditionalInfo { private final ItalianEInvoicing italianEInvoicing; + @JsonCreator + public TicketReservationInvoicingAdditionalInfo(@JsonProperty("italianEInvoicing") ItalianEInvoicing italianEInvoicing) { + this.italianEInvoicing = italianEInvoicing; + } + public boolean isEmpty() { return italianEInvoicing == null || italianEInvoicing.isEmpty(); } @@ -37,7 +42,6 @@ public boolean getEmpty() { // https://github.com/alfio-event/alf.io/issues/573 // @Getter - @AllArgsConstructor public static class ItalianEInvoicing { public enum ReferenceType { @@ -52,6 +56,19 @@ public enum ReferenceType { private final String pec; private final boolean splitPayment; + @JsonCreator + public ItalianEInvoicing(@JsonProperty("fiscalCode") String fiscalCode, + @JsonProperty("referenceType") ReferenceType referenceType, + @JsonProperty("addresseeCode") String addresseeCode, + @JsonProperty("pec") String pec, + @JsonProperty("splitPayment") boolean splitPayment) { + this.fiscalCode = fiscalCode; + this.referenceType = referenceType; + this.addresseeCode = addresseeCode; + this.pec = pec; + this.splitPayment = splitPayment; + } + public String getReferenceTypeAsString() { return referenceType == null ? "" : referenceType.toString(); } diff --git a/src/main/java/alfio/model/TicketWithCategory.java b/src/main/java/alfio/model/TicketWithCategory.java index 2fb97e48da..a501c936e4 100644 --- a/src/main/java/alfio/model/TicketWithCategory.java +++ b/src/main/java/alfio/model/TicketWithCategory.java @@ -16,16 +16,19 @@ */ package alfio.model; -import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; -@RequiredArgsConstructor public class TicketWithCategory implements TicketInfoContainer { @Delegate private final Ticket ticket; private final TicketCategory category; + public TicketWithCategory(Ticket ticket, TicketCategory category) { + this.ticket = ticket; + this.category = category; + } + public String getCategoryName() { return category != null ? category.getName() : null; } diff --git a/src/main/java/alfio/model/TotalPrice.java b/src/main/java/alfio/model/TotalPrice.java index 399ccc5aa4..a4c3125185 100644 --- a/src/main/java/alfio/model/TotalPrice.java +++ b/src/main/java/alfio/model/TotalPrice.java @@ -16,30 +16,13 @@ */ package alfio.model; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -@Getter -public class TotalPrice { - private final int priceWithVAT; - private final int VAT; - private final int discount; - private final int discountAppliedCount; - private final String currencyCode; - - @JsonCreator - public TotalPrice(@JsonProperty("priceWithVAT") int priceWithVAT, - @JsonProperty("vat") int VAT, - @JsonProperty("discount") int discount, - @JsonProperty("discountAppliedCount") int discountAppliedCount, - @JsonProperty("currencyCode") String currencyCode) { - this.priceWithVAT = priceWithVAT; - this.VAT = VAT; - this.discount = discount; - this.discountAppliedCount = discountAppliedCount; - this.currencyCode = currencyCode; - } +public record TotalPrice(@JsonProperty("priceWithVAT") int priceWithVAT, + @JsonProperty("vat") int VAT, + @JsonProperty("discount") int discount, + @JsonProperty("discountAppliedCount") int discountAppliedCount, + @JsonProperty("currencyCode") String currencyCode) { public boolean requiresPayment() { return this.priceWithVAT > 0; diff --git a/src/main/java/alfio/model/TransactionAndPaymentInfo.java b/src/main/java/alfio/model/TransactionAndPaymentInfo.java index 086e34d857..3534d8dfb1 100644 --- a/src/main/java/alfio/model/TransactionAndPaymentInfo.java +++ b/src/main/java/alfio/model/TransactionAndPaymentInfo.java @@ -19,15 +19,10 @@ import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.Transaction; -import lombok.AllArgsConstructor; -import lombok.Getter; -@Getter -@AllArgsConstructor -public class TransactionAndPaymentInfo { - private final PaymentProxy paymentMethod; - private final Transaction transaction; - private final PaymentInformation paymentInformation; +public record TransactionAndPaymentInfo(PaymentProxy paymentMethod, + Transaction transaction, + PaymentInformation paymentInformation) { public boolean isSupportRefund() { return paymentMethod != null && paymentMethod.isSupportRefund(); diff --git a/src/main/java/alfio/model/WaitingQueueSubscription.java b/src/main/java/alfio/model/WaitingQueueSubscription.java index 0336e8d7ec..f208636391 100644 --- a/src/main/java/alfio/model/WaitingQueueSubscription.java +++ b/src/main/java/alfio/model/WaitingQueueSubscription.java @@ -19,14 +19,13 @@ import alfio.util.LocaleUtil; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; import lombok.Getter; -import lombok.ToString; +import org.apache.commons.lang3.builder.ToStringBuilder; import java.time.ZonedDateTime; import java.util.Locale; import java.util.Optional; @Getter -@ToString public class WaitingQueueSubscription { public enum Status { @@ -83,4 +82,22 @@ public Locale getLocale() { public boolean isPreSales() { return Optional.ofNullable(subscriptionType).map(s -> s == Type.PRE_SALES).orElse(false); } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("id", id) + .append("creation", creation) + .append("eventId", eventId) + .append("status", status) + .append("fullName", fullName) + .append("firstName", firstName) + .append("lastName", lastName) + .append("emailAddress", emailAddress) + .append("reservationId", reservationId) + .append("userLanguage", userLanguage) + .append("selectedCategoryId", selectedCategoryId) + .append("subscriptionType", subscriptionType) + .toString(); + } } diff --git a/src/main/java/alfio/model/api/v1/admin/AttendeesByCategory.java b/src/main/java/alfio/model/api/v1/admin/AttendeesByCategory.java new file mode 100644 index 0000000000..09bc3ed61f --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/AttendeesByCategory.java @@ -0,0 +1,68 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +import alfio.model.modification.AttendeeData; +import alfio.model.modification.ReservationRequest; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import java.util.Map; + +public class AttendeesByCategory implements ReservationRequest { + + private final Integer ticketCategoryId; + private final Integer quantity; + private final List<AttendeeData> attendees; + // only for backwards compatibility + private final List<Map<String, String>> metadata; + + @JsonCreator + public AttendeesByCategory(@JsonProperty("ticketCategoryId") Integer ticketCategoryId, + @JsonProperty("quantity") Integer quantity, + @JsonProperty("attendees") List<AttendeeData> attendees, + @JsonProperty("metadata") List<Map<String, String>> metadata) { + this.ticketCategoryId = ticketCategoryId; + this.quantity = quantity; + this.attendees = attendees; + this.metadata = metadata; + } + + @Override + public Integer getTicketCategoryId() { + return ticketCategoryId; + } + + @Override + public Integer getQuantity() { + return quantity; + } + + @Override + public List<AttendeeData> getAttendees() { + if (attendees != null && !attendees.isEmpty()) { + return attendees; + } + return ReservationRequest.metadataToAttendeesList(getQuantity(), metadata); + } + + @Override + public List<Map<String, String>> getMetadata() { + return metadata; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java b/src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java new file mode 100644 index 0000000000..85100e34df --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java @@ -0,0 +1,52 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +import alfio.model.audit.ScanAudit; +import alfio.model.modification.AttendeeData; +import alfio.model.support.JSONData; +import alfio.util.Json; +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.List; + +public class CheckInLogEntry { + private final String ticketId; + private final AttendeeData attendeeData; + private final List<ScanAudit> audit; + + public CheckInLogEntry(@Column("t_uuid") String ticketId, + @Column("attendee_data") @JSONData AttendeeData attendeeData, + @Column("scans") String scansAsString) { + this.ticketId = ticketId; + this.attendeeData = attendeeData; + this.audit = Json.fromJson(scansAsString, new TypeReference<>() {}); + } + + public String getTicketId() { + return ticketId; + } + + public AttendeeData getAttendeeData() { + return attendeeData; + } + + public List<ScanAudit> getAudit() { + return audit; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/EventCreationRequest.java b/src/main/java/alfio/model/api/v1/admin/EventCreationRequest.java index 205ab18ae8..017aa62c3c 100644 --- a/src/main/java/alfio/model/api/v1/admin/EventCreationRequest.java +++ b/src/main/java/alfio/model/api/v1/admin/EventCreationRequest.java @@ -25,7 +25,8 @@ import alfio.model.modification.support.LocationDescriptor; import alfio.model.transaction.PaymentProxy; import alfio.model.user.Organization; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -42,28 +43,60 @@ import static org.apache.commons.collections.CollectionUtils.isEmpty; @Getter -@AllArgsConstructor public class EventCreationRequest{ - private String title; - private String slug; - private List<DescriptionRequest> description; - private Event.EventFormat format; - private LocationRequest location; - private String timezone; - private LocalDateTime startDate; - private LocalDateTime endDate; - private String websiteUrl; - private String termsAndConditionsUrl; - private String privacyPolicyUrl; - private String imageUrl; - private TicketRequest tickets; - private List<ExtensionSetting> extensionSettings; - private List<AttendeeAdditionalInfoRequest> additionalInfo; + private final String title; + private final String slug; + private final List<DescriptionRequest> description; + private final Event.EventFormat format; + private final LocationRequest location; + private final String timezone; + private final LocalDateTime startDate; + private final LocalDateTime endDate; + private final String websiteUrl; + private final String termsAndConditionsUrl; + private final String privacyPolicyUrl; + private final String imageUrl; + private final TicketRequest tickets; + private final List<ExtensionSetting> extensionSettings; + private final List<AttendeeAdditionalInfoRequest> additionalInfo; + + @JsonCreator + public EventCreationRequest(@JsonProperty("title") String title, + @JsonProperty("slug") String slug, + @JsonProperty("description") List<DescriptionRequest> description, + @JsonProperty("format") Event.EventFormat format, + @JsonProperty("location") LocationRequest location, + @JsonProperty("timezone") String timezone, + @JsonProperty("startDate") LocalDateTime startDate, + @JsonProperty("endDate") LocalDateTime endDate, + @JsonProperty("websiteUrl") String websiteUrl, + @JsonProperty("termsAndConditionsUrl") String termsAndConditionsUrl, + @JsonProperty("privacyPolicyUrl") String privacyPolicyUrl, + @JsonProperty("imageUrl") String imageUrl, + @JsonProperty("tickets") TicketRequest tickets, + @JsonProperty("extensionSettings") List<ExtensionSetting> extensionSettings, + @JsonProperty("additionalInfo") List<AttendeeAdditionalInfoRequest> additionalInfo) { + this.title = title; + this.slug = slug; + this.description = description; + this.format = format; + this.location = location; + this.timezone = timezone; + this.startDate = startDate; + this.endDate = endDate; + this.websiteUrl = websiteUrl; + this.termsAndConditionsUrl = termsAndConditionsUrl; + this.privacyPolicyUrl = privacyPolicyUrl; + this.imageUrl = imageUrl; + this.tickets = tickets; + this.extensionSettings = extensionSettings; + this.additionalInfo = additionalInfo; + } public EventModification toEventModification(Organization organization, UnaryOperator<String> slugGenerator, String imageRef) { - String slug = this.slug; - if(StringUtils.isBlank(slug)) { - slug = slugGenerator.apply(title); + String eventSlug = this.slug; + if(StringUtils.isBlank(eventSlug)) { + eventSlug = slugGenerator.apply(title); } int locales = description.stream() @@ -81,7 +114,7 @@ public EventModification toEventModification(Organization organization, UnaryOpe StringUtils.trimToNull(privacyPolicyUrl), null, imageRef, - slug, + eventSlug, title, organization.getId(), location.getFullAddress(), @@ -91,13 +124,13 @@ public EventModification toEventModification(Organization organization, UnaryOpe description.stream().collect(Collectors.toMap(DescriptionRequest::getLang,DescriptionRequest::getBody)), new DateTimeModification(startDate.toLocalDate(),startDate.toLocalTime()), new DateTimeModification(endDate.toLocalDate(),endDate.toLocalTime()), - tickets.freeOfCharge ? BigDecimal.ZERO : tickets.categories.stream().map(x -> x.price).max(BigDecimal::compareTo).orElse(BigDecimal.ONE).max(BigDecimal.ONE), + Boolean.TRUE.equals(tickets.freeOfCharge) ? BigDecimal.ZERO : tickets.categories.stream().map(x -> x.price).max(BigDecimal::compareTo).orElse(BigDecimal.ONE).max(BigDecimal.ONE), tickets.currency, tickets.max, tickets.taxPercentage, tickets.taxIncludedInPrice, tickets.paymentMethods, - tickets.categories.stream().map(CategoryRequest::toTicketCategoryModification).collect(Collectors.toList()), + getTicketCategoryModificationList(), tickets.freeOfCharge, new LocationDescriptor(timezone, location.getCoordinate().getLatitude(), location.getCoordinate().getLongitude(), null), locales, @@ -108,6 +141,14 @@ public EventModification toEventModification(Organization organization, UnaryOpe ); } + private List<TicketCategoryModification> getTicketCategoryModificationList() { + var result = new ArrayList<TicketCategoryModification>(); + for (int c = 0; c < tickets.categories.size(); c++) { + result.add(tickets.categories.get(c).toNewTicketCategoryModification(c + 1 )); + } + return List.copyOf(result); + } + private static <T> T first(T value,T other) { return Optional.ofNullable(value).orElse(other); } @@ -150,7 +191,7 @@ public EventModification toEventModificationUpdate(EventWithAdditionalInfo origi tickets != null ? first(tickets.taxPercentage,original.getVat()) : original.getVat(), tickets != null ? first(tickets.taxIncludedInPrice,original.isVatIncluded()) : original.isVatIncluded(), tickets != null ? first(tickets.paymentMethods, original.getAllowedPaymentProxies()) : original.getAllowedPaymentProxies(), - tickets != null && tickets.categories != null ? tickets.categories.stream().map(tc -> tc.toTicketCategoryModification(findCategoryId(original, tc))).collect(Collectors.toList()) : null, + tickets != null && tickets.categories != null ? createFromExistingCategories(original) : null, tickets != null ? first(tickets.freeOfCharge,original.isFreeOfCharge()) : original.isFreeOfCharge(), null, locales, @@ -161,69 +202,145 @@ public EventModification toEventModificationUpdate(EventWithAdditionalInfo origi ); } - private static Integer findCategoryId(EventWithAdditionalInfo event, CategoryRequest categoryRequest) { - return event.getTicketCategories().stream() - .filter(tc -> tc.getName().equals(categoryRequest.getName())) - .map(TicketCategoryWithAdditionalInfo::getId) - .findFirst() - .orElse(null); + private List<TicketCategoryModification> createFromExistingCategories(EventWithAdditionalInfo event) { + var result = new ArrayList<TicketCategoryModification>(tickets.categories.size()); + for(int c = 0; c < tickets.categories.size(); c++) { + var categoryRequest = tickets.categories.get(c); + var existing = findExistingCategory(event.getTicketCategories(), categoryRequest.getName(), categoryRequest.getId()); + if (existing.isPresent()) { + var category = existing.get(); + result.add(categoryRequest.toExistingTicketCategoryModification(category.getId(), category.getOrdinal())); + } else { + result.add(categoryRequest.toNewTicketCategoryModification(c + 1)); + } + } + return List.copyOf(result); + } + + public static Optional<TicketCategoryWithAdditionalInfo> findExistingCategory(List<TicketCategoryWithAdditionalInfo> categories, + String name, + Integer id) { + return categories.stream() + // if specified, ID takes precedence over name + .filter(oc -> id != null ? id == oc.getId() : oc.getName().equals(name)) + .findFirst(); } @Getter - @AllArgsConstructor public static class DescriptionRequest { - private String lang; - private String body; + private final String lang; + private final String body; + + @JsonCreator + public DescriptionRequest(@JsonProperty("lang") String lang, @JsonProperty("body") String body) { + this.lang = lang; + this.body = body; + } } @Getter - @AllArgsConstructor public static class LocationRequest { - private String fullAddress; - private CoordinateRequest coordinate; + private final String fullAddress; + private final CoordinateRequest coordinate; + + @JsonCreator + public LocationRequest(@JsonProperty("fullAddress") String fullAddress, @JsonProperty("coordinate") CoordinateRequest coordinate) { + this.fullAddress = fullAddress; + this.coordinate = coordinate; + } } @Getter - @AllArgsConstructor public static class TicketRequest { - private Boolean freeOfCharge; - private Integer max; - private String currency; - private BigDecimal taxPercentage; - private Boolean taxIncludedInPrice; - private List<PaymentProxy> paymentMethods; - private List<CategoryRequest> categories; - private List<PromoCodeRequest> promoCodes; + private final Boolean freeOfCharge; + private final Integer max; + private final String currency; + private final BigDecimal taxPercentage; + private final Boolean taxIncludedInPrice; + private final List<PaymentProxy> paymentMethods; + private final List<CategoryRequest> categories; + private final List<PromoCodeRequest> promoCodes; + + @JsonCreator + public TicketRequest(@JsonProperty("freeOfCharge") Boolean freeOfCharge, + @JsonProperty("max") Integer max, + @JsonProperty("currency") String currency, + @JsonProperty("taxPercentage") BigDecimal taxPercentage, + @JsonProperty("taxIncludedInPrice") Boolean taxIncludedInPrice, + @JsonProperty("paymentMethods") List<PaymentProxy> paymentMethods, + @JsonProperty("categories") List<CategoryRequest> categories, + @JsonProperty("promoCodes") List<PromoCodeRequest> promoCodes) { + this.freeOfCharge = freeOfCharge; + this.max = max; + this.currency = currency; + this.taxPercentage = taxPercentage; + this.taxIncludedInPrice = taxIncludedInPrice; + this.paymentMethods = paymentMethods; + this.categories = categories; + this.promoCodes = promoCodes; + } } @Getter - @AllArgsConstructor public static class CoordinateRequest { - private String latitude; - private String longitude; + private final String latitude; + private final String longitude; + + @JsonCreator + public CoordinateRequest(@JsonProperty("latitude") String latitude, @JsonProperty("longitude") String longitude) { + this.latitude = latitude; + this.longitude = longitude; + } } @Getter - @AllArgsConstructor public static class CategoryRequest { - private String name; - private List<DescriptionRequest> description; - private Integer maxTickets; - private boolean accessRestricted; - private BigDecimal price; - private LocalDateTime startSellingDate; - private LocalDateTime endSellingDate; - private String accessCode; - private CustomTicketValidityRequest customValidity; - private GroupLinkRequest groupLink; - private TicketCategory.TicketAccessType ticketAccessType; - - TicketCategoryModification toTicketCategoryModification() { - return toTicketCategoryModification(null); + private final Integer id; + private final String name; + private final List<DescriptionRequest> description; + private final Integer maxTickets; + private final boolean accessRestricted; + private final BigDecimal price; + private final LocalDateTime startSellingDate; + private final LocalDateTime endSellingDate; + private final String accessCode; + private final CustomTicketValidityRequest customValidity; + private final GroupLinkRequest groupLink; + private final TicketCategory.TicketAccessType ticketAccessType; + + @JsonCreator + public CategoryRequest(@JsonProperty("id") Integer id, + @JsonProperty("name") String name, + @JsonProperty("description") List<DescriptionRequest> description, + @JsonProperty("maxTickets") Integer maxTickets, + @JsonProperty("accessRestricted") boolean accessRestricted, + @JsonProperty("price") BigDecimal price, + @JsonProperty("startSellingDate") LocalDateTime startSellingDate, + @JsonProperty("endSellingDate") LocalDateTime endSellingDate, + @JsonProperty("accessCode") String accessCode, + @JsonProperty("customValidity") CustomTicketValidityRequest customValidity, + @JsonProperty("groupLink") GroupLinkRequest groupLink, + @JsonProperty("ticketAccessType") TicketCategory.TicketAccessType ticketAccessType) { + this.id = id; + this.name = name; + this.description = description; + this.maxTickets = maxTickets; + this.accessRestricted = accessRestricted; + this.price = price; + this.startSellingDate = startSellingDate; + this.endSellingDate = endSellingDate; + this.accessCode = accessCode; + this.customValidity = customValidity; + this.groupLink = groupLink; + this.ticketAccessType = ticketAccessType; + } + + TicketCategoryModification toNewTicketCategoryModification(int ordinal) { + return toExistingTicketCategoryModification(null, ordinal); } - TicketCategoryModification toTicketCategoryModification(Integer categoryId) { + TicketCategoryModification toExistingTicketCategoryModification(Integer categoryId, int ordinal) { int capacity = Optional.ofNullable(maxTickets).orElse(0); Optional<CustomTicketValidityRequest> customValidityOpt = Optional.ofNullable(customValidity); @@ -245,7 +362,7 @@ TicketCategoryModification toTicketCategoryModification(Integer categoryId) { customValidityOpt.flatMap(x -> Optional.ofNullable(x.checkInTo)).map(x -> new DateTimeModification(x.toLocalDate(),x.toLocalTime())).orElse(null), customValidityOpt.flatMap(x -> Optional.ofNullable(x.validityStart)).map(x -> new DateTimeModification(x.toLocalDate(),x.toLocalTime())).orElse(null), customValidityOpt.flatMap(x -> Optional.ofNullable(x.validityEnd)).map(x -> new DateTimeModification(x.toLocalDate(),x.toLocalTime())).orElse(null), - 0, + ordinal, null, null, AlfioMetadata.empty()); @@ -253,53 +370,111 @@ TicketCategoryModification toTicketCategoryModification(Integer categoryId) { } @Getter - @AllArgsConstructor public static class CustomTicketValidityRequest { - private LocalDateTime checkInFrom; - private LocalDateTime checkInTo; - private LocalDateTime validityStart; - private LocalDateTime validityEnd; + private final LocalDateTime checkInFrom; + private final LocalDateTime checkInTo; + private final LocalDateTime validityStart; + private final LocalDateTime validityEnd; + + @JsonCreator + public CustomTicketValidityRequest(@JsonProperty("checkInFrom") LocalDateTime checkInFrom, + @JsonProperty("checkInTo") LocalDateTime checkInTo, + @JsonProperty("validityStart") LocalDateTime validityStart, + @JsonProperty("validityEnd") LocalDateTime validityEnd) { + this.checkInFrom = checkInFrom; + this.checkInTo = checkInTo; + this.validityStart = validityStart; + this.validityEnd = validityEnd; + } } @Getter - @AllArgsConstructor public static class PromoCodeRequest { - private String name; - private LocalDateTime validFrom; - private LocalDateTime validTo; - private PromoCodeDiscount.DiscountType discountType; - private int discount; + private final String name; + private final LocalDateTime validFrom; + private final LocalDateTime validTo; + private final PromoCodeDiscount.DiscountType discountType; + private final int discount; + + @JsonCreator + public PromoCodeRequest(@JsonProperty("name") String name, + @JsonProperty("validFrom") LocalDateTime validFrom, + @JsonProperty("validTo") LocalDateTime validTo, + @JsonProperty("discountType") PromoCodeDiscount.DiscountType discountType, + @JsonProperty("discount") int discount) { + this.name = name; + this.validFrom = validFrom; + this.validTo = validTo; + this.discountType = discountType; + this.discount = discount; + } } @Getter - @AllArgsConstructor public static class ExtensionSetting { - private String extensionId; - private String key; - private String value; + private final String extensionId; + private final String key; + private final String value; + + @JsonCreator + public ExtensionSetting(@JsonProperty("extensionId") String extensionId, + @JsonProperty("key") String key, + @JsonProperty("value") String value) { + this.extensionId = extensionId; + this.key = key; + this.value = value; + } } @Getter - @AllArgsConstructor public static class GroupLinkRequest { - private Integer groupId; - private LinkedGroup.Type type; - private LinkedGroup.MatchType matchType; - private Integer maxAllocation; + private final Integer groupId; + private final LinkedGroup.Type type; + private final LinkedGroup.MatchType matchType; + private final Integer maxAllocation; + + @JsonCreator + public GroupLinkRequest(@JsonProperty("groupId") Integer groupId, + @JsonProperty("type") LinkedGroup.Type type, + @JsonProperty("matchType") LinkedGroup.MatchType matchType, + @JsonProperty("maxAllocation") Integer maxAllocation) { + this.groupId = groupId; + this.type = type; + this.matchType = matchType; + this.maxAllocation = maxAllocation; + } } @Getter - @AllArgsConstructor public static class AttendeeAdditionalInfoRequest { - private Integer ordinal; - private String name; - private AdditionalInfoType type; - private Boolean required; - private List<DescriptionRequest> label; - private List<DescriptionRequest> placeholder; - private List<RestrictedValueRequest> restrictedValues; - private ContentLengthRequest contentLength; + private final Integer ordinal; + private final String name; + private final AdditionalInfoType type; + private final Boolean required; + private final List<DescriptionRequest> label; + private final List<DescriptionRequest> placeholder; + private final List<RestrictedValueRequest> restrictedValues; + private final ContentLengthRequest contentLength; + + @JsonCreator + public AttendeeAdditionalInfoRequest(@JsonProperty("ordinal") Integer ordinal, + @JsonProperty("name") String name, + @JsonProperty("type") AdditionalInfoType type, + @JsonProperty("required") Boolean required, + @JsonProperty("label") List<DescriptionRequest> label, + @JsonProperty("placeholder") List<DescriptionRequest> placeholder, + @JsonProperty("restrictedValues") List<RestrictedValueRequest> restrictedValues, + @JsonProperty("contentLength") ContentLengthRequest contentLength) { + this.ordinal = ordinal; + this.name = name; + this.type = type; + this.required = required; + this.label = label; + this.placeholder = placeholder; + this.restrictedValues = restrictedValues; + this.contentLength = contentLength; + } private EventModification.AdditionalField toAdditionalField(int ordinal) { @@ -307,9 +482,9 @@ private EventModification.AdditionalField toAdditionalField(int ordinal) { String code = type != null ? type.code : AdditionalInfoType.GENERIC_TEXT.code; Integer minLength = contentLength != null ? contentLength.min : null; Integer maxLength = contentLength != null ? contentLength.max : null; - List<EventModification.RestrictedValue> restrictedValues = null; + List<EventModification.RestrictedValue> cleanRestrictedValues = null; if(!isEmpty(this.restrictedValues)) { - restrictedValues = this.restrictedValues.stream().map(rv -> new EventModification.RestrictedValue(rv.value, rv.enabled)).collect(Collectors.toList()); + cleanRestrictedValues = this.restrictedValues.stream().map(rv -> new EventModification.RestrictedValue(rv.value, rv.enabled)).toList(); } return new EventModification.AdditionalField( @@ -321,45 +496,45 @@ private EventModification.AdditionalField toAdditionalField(int ordinal) { false, minLength, maxLength, - restrictedValues, + cleanRestrictedValues, toDescriptionMap(orEmpty(label), orEmpty(placeholder), orEmpty(this.restrictedValues)), null,//TODO: linkedAdditionalService null);//TODO: linkedCategoryIds } + + private static Map<String, EventModification.Description> toDescriptionMap(List<DescriptionRequest> label, + List<DescriptionRequest> placeholder, + List<RestrictedValueRequest> restrictedValues) { + Map<String, String> labelsByLang = label.stream().collect(Collectors.toMap(DescriptionRequest::getLang, DescriptionRequest::getBody)); + Map<String, String> placeholdersByLang = placeholder.stream().collect(Collectors.toMap(DescriptionRequest::getLang, DescriptionRequest::getBody)); + Map<String, List<Triple<String, String, String>>> valuesByLang = restrictedValues.stream() + .flatMap(rv -> rv.descriptions.stream().map(rvd -> Triple.of(rvd.lang, rv.value, rvd.body))) + .collect(Collectors.groupingBy(Triple::getLeft)); + + + Set<String> keys = new HashSet<>(labelsByLang.keySet()); + keys.addAll(placeholdersByLang.keySet()); + keys.addAll(valuesByLang.keySet()); + + return keys.stream() + .map(lang -> { + Map<String, String> rvsMap = valuesByLang.getOrDefault(lang, emptyList()).stream().collect(Collectors.toMap(Triple::getMiddle, Triple::getRight)); + return Pair.of(lang, new EventModification.Description(labelsByLang.get(lang), placeholdersByLang.get(lang), rvsMap)); + }).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); + } } private static <T> List<T> orEmpty(List<T> input) { return isEmpty(input) ? emptyList() : input; } - private static Map<String, EventModification.Description> toDescriptionMap(List<DescriptionRequest> label, - List<DescriptionRequest> placeholder, - List<RestrictedValueRequest> restrictedValues) { - Map<String, String> labelsByLang = label.stream().collect(Collectors.toMap(DescriptionRequest::getLang, DescriptionRequest::getBody)); - Map<String, String> placeholdersByLang = placeholder.stream().collect(Collectors.toMap(DescriptionRequest::getLang, DescriptionRequest::getBody)); - Map<String, List<Triple<String, String, String>>> valuesByLang = restrictedValues.stream() - .flatMap(rv -> rv.descriptions.stream().map(rvd -> Triple.of(rvd.lang, rv.value, rvd.body))) - .collect(Collectors.groupingBy(Triple::getLeft)); - - - Set<String> keys = new HashSet<>(labelsByLang.keySet()); - keys.addAll(placeholdersByLang.keySet()); - keys.addAll(valuesByLang.keySet()); - - return keys.stream() - .map(lang -> { - Map<String, String> rvsMap = valuesByLang.getOrDefault(lang, emptyList()).stream().collect(Collectors.toMap(Triple::getMiddle, Triple::getRight)); - return Pair.of(lang, new EventModification.Description(labelsByLang.get(lang), placeholdersByLang.get(lang), rvsMap)); - }).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - } - private static List<EventModification.AdditionalField> toAdditionalFields(List<AttendeeAdditionalInfoRequest> additionalInfoRequests) { if(isEmpty(additionalInfoRequests)) { return emptyList(); } AtomicInteger counter = new AtomicInteger(1); - return additionalInfoRequests.stream().map(air -> air.toAdditionalField(counter.getAndIncrement())).collect(Collectors.toList()); + return additionalInfoRequests.stream().map(air -> air.toAdditionalField(counter.getAndIncrement())).toList(); } @Getter @@ -385,21 +560,30 @@ enum AdditionalInfoType { public static final Set<String> WITH_RESTRICTED_VALUES = Set.of(AdditionalInfoType.LIST_BOX.code, AdditionalInfoType.CHECKBOX.code, AdditionalInfoType.RADIO.code); @Getter - @AllArgsConstructor public static class ContentLengthRequest { - private Integer min; - private Integer max; + private final Integer min; + private final Integer max; + + @JsonCreator + public ContentLengthRequest(@JsonProperty("min") Integer min, @JsonProperty("max") Integer max) { + this.min = min; + this.max = max; + } } @Getter - @AllArgsConstructor public static class RestrictedValueRequest { - - private String value; - private Boolean enabled; - private List<DescriptionRequest> descriptions; - + private final String value; + private final Boolean enabled; + private final List<DescriptionRequest> descriptions; + + @JsonCreator + public RestrictedValueRequest(@JsonProperty("value") String value, + @JsonProperty("enabled") Boolean enabled, + @JsonProperty("descriptions") List<DescriptionRequest> descriptions) { + this.value = value; + this.enabled = enabled; + this.descriptions = descriptions; + } } - - } \ No newline at end of file diff --git a/src/main/java/alfio/model/api/v1/admin/LinkedSubscriptions.java b/src/main/java/alfio/model/api/v1/admin/LinkedSubscriptions.java new file mode 100644 index 0000000000..b40016d3b8 --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/LinkedSubscriptions.java @@ -0,0 +1,39 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +public class LinkedSubscriptions { + private final String eventSlug; + private final List<UUID> subscriptions; + + public LinkedSubscriptions(String eventSlug, List<UUID> subscriptions) { + this.eventSlug = eventSlug; + this.subscriptions = subscriptions; + } + + public String getEventSlug() { + return eventSlug; + } + + public List<UUID> getSubscriptions() { + return Objects.requireNonNullElse(subscriptions, List.of()); + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/ReservationAPICreationRequest.java b/src/main/java/alfio/model/api/v1/admin/ReservationAPICreationRequest.java new file mode 100644 index 0000000000..028cc9366d --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/ReservationAPICreationRequest.java @@ -0,0 +1,25 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +public interface ReservationAPICreationRequest { + ReservationUser getUser(); + + String getLanguage(); + + ReservationConfiguration getReservationConfiguration(); +} diff --git a/src/main/java/alfio/model/api/v1/admin/ReservationConfiguration.java b/src/main/java/alfio/model/api/v1/admin/ReservationConfiguration.java new file mode 100644 index 0000000000..45cb17427e --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/ReservationConfiguration.java @@ -0,0 +1,47 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ReservationConfiguration { + private final boolean hideContactData; + private final boolean hideConfirmationButtons; + private final boolean lockEmailEdit; + + @JsonCreator + public ReservationConfiguration(@JsonProperty("hideContactData") Boolean hideContactData, + @JsonProperty("hideConfirmationButtons") Boolean hideConfirmationButtons, + @JsonProperty("lockEmailEdit") Boolean lockEmailEdit) { + this.hideContactData = Boolean.TRUE.equals(hideContactData); + this.hideConfirmationButtons = Boolean.TRUE.equals(hideConfirmationButtons); + this.lockEmailEdit = Boolean.TRUE.equals(lockEmailEdit); + } + + public boolean isHideContactData() { + return hideContactData; + } + + public boolean isHideConfirmationButtons() { + return hideConfirmationButtons; + } + + public boolean isLockEmailEdit() { + return lockEmailEdit; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/SubscriptionDescriptorModificationRequest.java b/src/main/java/alfio/model/api/v1/admin/SubscriptionDescriptorModificationRequest.java new file mode 100644 index 0000000000..14235e638e --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/SubscriptionDescriptorModificationRequest.java @@ -0,0 +1,194 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +import alfio.model.PriceContainer; +import alfio.model.api.v1.admin.subscription.CustomPeriodTerm; +import alfio.model.api.v1.admin.subscription.EntryBasedTerm; +import alfio.model.api.v1.admin.subscription.StandardPeriodTerm; +import alfio.model.api.v1.admin.subscription.SubscriptionTerm; +import alfio.model.modification.SubscriptionDescriptorModification; +import alfio.model.result.ErrorCode; +import alfio.model.result.Result; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType; +import alfio.model.transaction.PaymentProxy; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static alfio.util.LocaleUtil.atZone; +import static java.util.Objects.requireNonNullElse; + +public class SubscriptionDescriptorModificationRequest { + + private static final Logger log = LoggerFactory.getLogger(SubscriptionDescriptorModificationRequest.class); + public static final String TERM_STANDARD = "standard"; + public static final String TERM_NUM_ENTRIES = "numEntries"; + public static final String TERM_CUSTOM = "custom"; + + private final SubscriptionUsageType usageType; + private final SubscriptionTerm term; + + private final List<EventCreationRequest.DescriptionRequest> title; + private final List<EventCreationRequest.DescriptionRequest> description; + private final Integer maxAvailable; + private final LocalDateTime onSaleFrom; + private final LocalDateTime onSaleTo; + private final BigDecimal price; + private final BigDecimal taxPercentage; + private final PriceContainer.VatStatus taxPolicy; + private final String currencyCode; + private final Boolean isPublic; + private final String imageUrl; + private final String termType; + + private final String termsAndConditionsUrl; + private final String privacyPolicyUrl; + private final String timezone; + private final Boolean supportsTicketsGeneration; + private final List<PaymentProxy> paymentMethods; + + + + @JsonCreator + public SubscriptionDescriptorModificationRequest(@JsonProperty("usageType") SubscriptionUsageType usageType, + @JsonProperty("termType") String termType, + @JsonTypeInfo(use= JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "termType") + @JsonSubTypes({ + // if termType is "standard", then we expect an instance of StandardPeriodTerm + @JsonSubTypes.Type(value = StandardPeriodTerm.class, name = TERM_STANDARD), + // if termType is "numEntries", then we expect an instance of EntryBasedTerm + @JsonSubTypes.Type(value = EntryBasedTerm.class, name = TERM_NUM_ENTRIES), + // if termType is "custom", then we expect an instance of CustomPeriodTerm + @JsonSubTypes.Type(value = CustomPeriodTerm.class, name = TERM_CUSTOM) + }) + @JsonProperty("term") SubscriptionTerm term, + @JsonProperty("title") List<EventCreationRequest.DescriptionRequest> title, + @JsonProperty("description") List<EventCreationRequest.DescriptionRequest> description, + @JsonProperty("maxAvailable") Integer maxAvailable, + @JsonProperty("onSaleFrom") LocalDateTime onSaleFrom, + @JsonProperty("onSaleTo") LocalDateTime onSaleTo, + @JsonProperty("price") BigDecimal price, + @JsonProperty("taxPercentage") BigDecimal taxPercentage, + @JsonProperty("taxPolicy") PriceContainer.VatStatus taxPolicy, + @JsonProperty("currencyCode") String currencyCode, + @JsonProperty("isPublic") Boolean isPublic, + @JsonProperty("imageUrl") String imageUrl, + @JsonProperty("termsAndConditionsUrl") String termsAndConditionsUrl, + @JsonProperty("privacyPolicyUrl") String privacyPolicyUrl, + @JsonProperty("timezone") String timezone, + @JsonProperty("supportsTicketsGeneration") Boolean supportsTicketsGeneration, + @JsonProperty("paymentMethods") List<PaymentProxy> paymentMethods) { + this.usageType = usageType; + this.termType = termType; + this.term = term; + this.title = requireNonNullElse(title, List.of()); + this.description = requireNonNullElse(description, List.of()); + this.maxAvailable = maxAvailable; + this.onSaleFrom = onSaleFrom; + this.onSaleTo = onSaleTo; + this.price = price; + this.taxPercentage = taxPercentage; + this.taxPolicy = taxPolicy; + this.currencyCode = currencyCode; + this.isPublic = isPublic; + this.imageUrl = imageUrl; + this.termsAndConditionsUrl = termsAndConditionsUrl; + this.privacyPolicyUrl = privacyPolicyUrl; + this.timezone = timezone; + this.supportsTicketsGeneration = supportsTicketsGeneration; + this.paymentMethods = requireNonNullElse(paymentMethods, List.of()); + } + + public Result<SubscriptionDescriptorModification> toDescriptorModification(UUID id, int organizationId, String fileBlobId) { + var zoneIdOptional = getZoneId(); + return new Result.Builder<SubscriptionDescriptorModification>() + .checkPrecondition(zoneIdOptional::isPresent, ErrorCode.custom("timezone", "Timezone is mandatory")) + .checkPrecondition(() -> usageType != null, ErrorCode.custom("usageType", "UsageType is mandatory")) + .checkPrecondition(() -> term != null && term.validate(), ErrorCode.custom("term", "Term is not valid")) + .build(() -> { + var zoneId = zoneIdOptional.orElseThrow(); + return new SubscriptionDescriptorModification( + id, + title.stream().collect(Collectors.toMap(EventCreationRequest.DescriptionRequest::getLang, EventCreationRequest.DescriptionRequest::getBody)), + description.stream().collect(Collectors.toMap(EventCreationRequest.DescriptionRequest::getLang, EventCreationRequest.DescriptionRequest::getBody)), + requireNonNullElse(maxAvailable, -1), + atZone(onSaleFrom, zoneId), + atZone(onSaleTo, zoneId), + price, + taxPercentage, + taxPolicy, + currencyCode, + Boolean.TRUE.equals(isPublic), + organizationId, + requireNonNullElse(term.getNumEntries(), -1), + getValidityType(), + term.getTimeUnit(), + term.getUnits(), + atZone(term.getValidityFrom(), zoneId), + atZone(term.getValidityTo(), zoneId), + usageType, + termsAndConditionsUrl, + privacyPolicyUrl, + fileBlobId, + paymentMethods, + zoneId, + Boolean.TRUE.equals(supportsTicketsGeneration) + ); + }); + } + + private Optional<ZoneId> getZoneId() { + if (StringUtils.isEmpty(timezone)) { + return Optional.empty(); + } + try { + return Optional.of(ZoneId.of(timezone)); + } catch(Exception ex) { + log.warn("Error while parsing timezone", ex); + return Optional.empty(); + } + } + + private SubscriptionDescriptor.SubscriptionValidityType getValidityType() { + if(TERM_STANDARD.equals(termType)) { + return SubscriptionDescriptor.SubscriptionValidityType.STANDARD; + } else if(TERM_CUSTOM.equals(termType)) { + return SubscriptionDescriptor.SubscriptionValidityType.CUSTOM; + } else if(TERM_NUM_ENTRIES.equals(termType)) { + return SubscriptionDescriptor.SubscriptionValidityType.NOT_SET; + } + return null; + } + + public String getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/SubscriptionReservationCreationRequest.java b/src/main/java/alfio/model/api/v1/admin/SubscriptionReservationCreationRequest.java new file mode 100644 index 0000000000..9b15c94e53 --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/SubscriptionReservationCreationRequest.java @@ -0,0 +1,76 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin; + +import alfio.model.api.v1.admin.subscription.SubscriptionConfiguration; +import alfio.model.metadata.SubscriptionMetadata; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; +import java.util.Objects; + +public class SubscriptionReservationCreationRequest implements ReservationAPICreationRequest { + private final Map<String, String> metadata; + private final ReservationUser user; + private final String language; + private final ReservationConfiguration reservationConfiguration; + private final SubscriptionConfiguration subscriptionConfiguration; + + @JsonCreator + public SubscriptionReservationCreationRequest(@JsonProperty("metadata") Map<String, String> metadata, + @JsonProperty("user") ReservationUser user, + @JsonProperty("language") String language, + @JsonProperty("reservationConfiguration") ReservationConfiguration reservationConfiguration, + @JsonProperty("subscriptionConfiguration") SubscriptionConfiguration subscriptionConfiguration) { + this.metadata = metadata; + this.user = user; + this.language = language; + this.reservationConfiguration = reservationConfiguration; + this.subscriptionConfiguration = Objects.requireNonNullElseGet(subscriptionConfiguration, SubscriptionConfiguration::defaultConfiguration); + } + + public Map<String, String> getMetadata() { + return metadata; + } + + @Override + public ReservationUser getUser() { + return user; + } + + @Override + public String getLanguage() { + return language; + } + + @Override + public ReservationConfiguration getReservationConfiguration() { + return reservationConfiguration; + } + + public SubscriptionConfiguration getSubscriptionConfiguration() { + return subscriptionConfiguration; + } + + public SubscriptionMetadata getMetadataOrNull() { + if (metadata != null && !metadata.isEmpty()) { + return new SubscriptionMetadata(metadata, subscriptionConfiguration); + } + return null; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/ReservationCreationRequest.java b/src/main/java/alfio/model/api/v1/admin/TicketReservationCreationRequest.java similarity index 55% rename from src/main/java/alfio/model/api/v1/admin/ReservationCreationRequest.java rename to src/main/java/alfio/model/api/v1/admin/TicketReservationCreationRequest.java index 4996ff4bbf..4c687d66b8 100644 --- a/src/main/java/alfio/model/api/v1/admin/ReservationCreationRequest.java +++ b/src/main/java/alfio/model/api/v1/admin/TicketReservationCreationRequest.java @@ -18,31 +18,37 @@ import alfio.controller.form.ReservationCreate; import alfio.model.modification.AdditionalServiceReservationModification; -import alfio.model.modification.TicketReservationModification; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; import java.util.List; -public class ReservationCreationRequest implements ReservationCreate { +public class TicketReservationCreationRequest implements ReservationCreate<AttendeesByCategory>, ReservationAPICreationRequest { - private final List<TicketReservationModification> tickets; + private final List<AttendeesByCategory> tickets; private final List<AdditionalServiceReservationModification> additionalServices; + private final ReservationConfiguration reservationConfiguration; private final ReservationUser user; private final String promoCode; private final String language; + private final String subscriptionId; @JsonCreator - public ReservationCreationRequest(@JsonProperty("tickets") List<TicketReservationModification> tickets, - @JsonProperty("additionalServices") List<AdditionalServiceReservationModification> additionalServices, - @JsonProperty("user") ReservationUser user, - @JsonProperty("promoCode") String promoCode, - @JsonProperty("language") String language) { + public TicketReservationCreationRequest(@JsonProperty("tickets") List<AttendeesByCategory> tickets, + @JsonProperty("additionalServices") List<AdditionalServiceReservationModification> additionalServices, + @JsonProperty("configuration") ReservationConfiguration reservationConfiguration, + @JsonProperty("user") ReservationUser user, + @JsonProperty("promoCode") String promoCode, + @JsonProperty("language") String language, + @JsonProperty("subscriptionId") String subscriptionId) { this.tickets = tickets; this.additionalServices = additionalServices; + this.reservationConfiguration = reservationConfiguration; this.user = user; this.promoCode = promoCode; this.language = language; + this.subscriptionId = StringUtils.trimToNull(subscriptionId); } @@ -52,7 +58,7 @@ public String getPromoCode() { } @Override - public List<TicketReservationModification> getTickets() { + public List<AttendeesByCategory> getTickets() { return tickets; } @@ -66,11 +72,22 @@ public String getCaptcha() { return null; } + @Override public String getLanguage() { return language; } + @Override public ReservationUser getUser() { return user; } + + @Override + public ReservationConfiguration getReservationConfiguration() { + return reservationConfiguration; + } + + public String getSubscriptionId() { + return subscriptionId; + } } diff --git a/src/main/java/alfio/model/api/v1/admin/subscription/CustomPeriodTerm.java b/src/main/java/alfio/model/api/v1/admin/subscription/CustomPeriodTerm.java new file mode 100644 index 0000000000..1c98535275 --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/subscription/CustomPeriodTerm.java @@ -0,0 +1,53 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin.subscription; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.LocalDateTime; + +public class CustomPeriodTerm implements SubscriptionTerm { + + private final LocalDateTime validityFrom; + private final LocalDateTime validityTo; + + @JsonCreator + public CustomPeriodTerm(@JsonProperty("validityFrom") LocalDateTime validityFrom, + @JsonProperty("validityTo") LocalDateTime validityTo) { + this.validityFrom = validityFrom; + this.validityTo = validityTo; + } + + @Override + public boolean validate() { + return validityFrom != null + && validityTo != null + && validityFrom.isBefore(validityTo); + } + + @Override + public LocalDateTime getValidityFrom() { + return validityFrom; + } + + @Override + public LocalDateTime getValidityTo() { + return validityTo; + } + +} diff --git a/src/main/java/alfio/model/api/v1/admin/subscription/EntryBasedTerm.java b/src/main/java/alfio/model/api/v1/admin/subscription/EntryBasedTerm.java new file mode 100644 index 0000000000..32900d06a1 --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/subscription/EntryBasedTerm.java @@ -0,0 +1,40 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin.subscription; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class EntryBasedTerm implements SubscriptionTerm { + + private final Integer numEntries; + + @JsonCreator + public EntryBasedTerm(@JsonProperty("numEntries") Integer numEntries) { + this.numEntries = numEntries; + } + + @Override + public boolean validate() { + return numEntries != null && numEntries > 0; + } + + @Override + public Integer getNumEntries() { + return numEntries; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/subscription/StandardPeriodTerm.java b/src/main/java/alfio/model/api/v1/admin/subscription/StandardPeriodTerm.java new file mode 100644 index 0000000000..e9d8207a5a --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/subscription/StandardPeriodTerm.java @@ -0,0 +1,49 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin.subscription; + +import alfio.model.subscription.SubscriptionDescriptor.SubscriptionTimeUnit; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StandardPeriodTerm implements SubscriptionTerm { + + private final SubscriptionTimeUnit timeUnit; + private final Integer units; + + @JsonCreator + public StandardPeriodTerm(@JsonProperty("timeUnit") SubscriptionTimeUnit timeUnit, + @JsonProperty("units") Integer units) { + this.timeUnit = timeUnit; + this.units = units; + } + + @Override + public boolean validate() { + return timeUnit != null && units != null && units > 0; + } + + @Override + public SubscriptionTimeUnit getTimeUnit() { + return timeUnit; + } + + @Override + public Integer getUnits() { + return units; + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/subscription/SubscriptionConfiguration.java b/src/main/java/alfio/model/api/v1/admin/subscription/SubscriptionConfiguration.java new file mode 100644 index 0000000000..c1a05e5ad0 --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/subscription/SubscriptionConfiguration.java @@ -0,0 +1,42 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin.subscription; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +public class SubscriptionConfiguration { + /** + * Display PIN information on reservation page and PDF. Default is {@code true} + */ + private final boolean displayPin; + + @JsonCreator + public SubscriptionConfiguration(@JsonProperty("displayPin") Boolean displayPin) { + this.displayPin = Objects.requireNonNullElse(displayPin, true); + } + + public boolean isDisplayPin() { + return displayPin; + } + + public static SubscriptionConfiguration defaultConfiguration() { + return new SubscriptionConfiguration(true); + } +} diff --git a/src/main/java/alfio/model/api/v1/admin/subscription/SubscriptionTerm.java b/src/main/java/alfio/model/api/v1/admin/subscription/SubscriptionTerm.java new file mode 100644 index 0000000000..5e13d9cb2a --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/subscription/SubscriptionTerm.java @@ -0,0 +1,48 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.api.v1.admin.subscription; + +import alfio.model.subscription.SubscriptionDescriptor; + +import java.time.LocalDateTime; + +public interface SubscriptionTerm { + /** + * Validate this subscription term + * @return {@code true} if valid + */ + boolean validate(); + + default SubscriptionDescriptor.SubscriptionTimeUnit getTimeUnit() { + return null; + } + + default Integer getUnits() { + return null; + } + default Integer getNumEntries() { + return null; + } + + default LocalDateTime getValidityFrom() { + return null; + } + + default LocalDateTime getValidityTo() { + return null; + } +} diff --git a/src/main/java/alfio/model/audit/ScanAudit.java b/src/main/java/alfio/model/audit/ScanAudit.java index b21640bc66..d0fef5e6e9 100644 --- a/src/main/java/alfio/model/audit/ScanAudit.java +++ b/src/main/java/alfio/model/audit/ScanAudit.java @@ -18,36 +18,25 @@ import alfio.manager.support.CheckInStatus; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; -import java.time.ZonedDateTime; +import java.time.LocalDateTime; -@Getter -public class ScanAudit { +public record ScanAudit( + @JsonProperty("ticketUuid") @Column("ticket_uuid") String ticketUuid, + @JsonProperty("scanTimestamp") @Column("scan_ts") LocalDateTime scanTimestamp, + @JsonProperty("username") @Column("username") String username, + @JsonProperty("checkInStatus") @Column("check_in_status") CheckInStatus checkInStatus, + @JsonProperty("operation") @Column("operation") ScanAudit.Operation operation) { public enum Operation { SCAN, REVERT } - private final String ticketUuid; - private final int eventId; - private final ZonedDateTime scanTimestamp; - private final String username; - private final CheckInStatus checkInStatus; - private final Operation operation; - - public ScanAudit(@Column("ticket_uuid") String ticketUuid, - @Column("event_id_fk") int eventId, - @Column("scan_ts") ZonedDateTime scanTimestamp, - @Column("username") String username, - @Column("check_in_status") CheckInStatus checkInStatus, - @Column("operation") Operation operation) { - this.ticketUuid = ticketUuid; - this.eventId = eventId; - this.scanTimestamp = scanTimestamp; - this.username = username; - this.checkInStatus = checkInStatus; - this.operation = operation; + @JsonIgnore + public String ticketUuid() { + return ticketUuid; } } diff --git a/src/main/java/alfio/model/checkin/AttendeeSearchResults.java b/src/main/java/alfio/model/checkin/AttendeeSearchResults.java new file mode 100644 index 0000000000..ac9e2e68c1 --- /dev/null +++ b/src/main/java/alfio/model/checkin/AttendeeSearchResults.java @@ -0,0 +1,116 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.checkin; + +import alfio.model.Ticket; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class AttendeeSearchResults { + private final int totalResults; + private final int checkedIn; + private final int totalPages; + private final int numPage; + private final List<Attendee> attendees; + + public AttendeeSearchResults(int totalResults, + int checkedIn, + int totalPages, + int numPage, + List<Attendee> attendees) { + this.totalResults = totalResults; + this.checkedIn = checkedIn; + this.totalPages = totalPages; + this.numPage = numPage; + this.attendees = attendees; + } + + public int getTotalResults() { + return totalResults; + } + + public int getCheckedIn() { + return checkedIn; + } + + public List<Attendee> getAttendees() { + return attendees; + } + + public boolean hasMorePages() { + return numPage < totalPages - 1; + } + + public int getTotalPages() { + return totalPages; + } + + public int getNumPage() { + return numPage; + } + + public static class Attendee { + private final String uuid; + private final String firstName; + private final String lastName; + private final String categoryName; + private final Map<String, List<String>> additionalInfo; + private final Ticket.TicketStatus ticketStatus; + private final String amountToPay; + + public Attendee(String uuid, + String firstName, String lastName, String categoryName, Map<String, List<String>> additionalInfo, Ticket.TicketStatus ticketStatus, String amountToPay) { + this.uuid = uuid; + this.firstName = firstName; + this.lastName = lastName; + this.categoryName = categoryName; + this.additionalInfo = additionalInfo; + this.ticketStatus = ticketStatus; + this.amountToPay = amountToPay; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getCategoryName() { + return categoryName; + } + + public Map<String, List<String>> getAdditionalInfo() { + return Objects.requireNonNullElse(additionalInfo, Map.of()); + } + + public Ticket.TicketStatus getTicketStatus() { + return ticketStatus; + } + + public String getAmountToPay() { + return amountToPay; + } + + public String getUuid() { + return uuid; + } + } +} diff --git a/src/main/java/alfio/model/checkin/AttendeeSearchResultsCount.java b/src/main/java/alfio/model/checkin/AttendeeSearchResultsCount.java new file mode 100644 index 0000000000..b4bb460640 --- /dev/null +++ b/src/main/java/alfio/model/checkin/AttendeeSearchResultsCount.java @@ -0,0 +1,38 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.checkin; + +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; + +public class AttendeeSearchResultsCount { + private final int total; + private final int checkedIn; + + public AttendeeSearchResultsCount(@Column("total") int total, + @Column("checked_in") int checkedIn) { + this.total = total; + this.checkedIn = checkedIn; + } + + public int getTotal() { + return total; + } + + public int getCheckedIn() { + return checkedIn; + } +} diff --git a/src/main/java/alfio/model/checkin/CheckInFullInfo.java b/src/main/java/alfio/model/checkin/CheckInFullInfo.java new file mode 100644 index 0000000000..569d59fcaf --- /dev/null +++ b/src/main/java/alfio/model/checkin/CheckInFullInfo.java @@ -0,0 +1,162 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.checkin; + +import alfio.model.*; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.support.Array; +import alfio.model.support.JSONData; +import alfio.model.transaction.PaymentProxy; +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import lombok.Getter; + +import java.math.BigDecimal; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Getter +public class CheckInFullInfo { + + private final Ticket ticket; + private final TicketReservation ticketReservation; + private final BillingDetails billingDetails; + private final TicketCategory ticketCategory; + private final AlfioMetadata categoryMetadata; + private final Map<String, List<String>> ticketAdditionalInfo; + private final EventWithCheckInInfo eventWithCheckInInfo; + + + public CheckInFullInfo(@Column("t_id") int id, + @Column("t_uuid") String uuid, + @Column("t_creation") ZonedDateTime creation, + @Column("t_category_id") int categoryId, + @Column("t_status") String status, + @Column("t_event_id") int eventId, + @Column("t_src_price_cts") int ticketSrcPriceCts, + @Column("t_final_price_cts") int ticketFinalPriceCts, + @Column("t_vat_cts") int ticketVatCts, + @Column("t_discount_cts") int ticketDiscountCts, + @Column("t_tickets_reservation_id") String ticketsReservationId, + @Column("t_full_name") String fullName, + @Column("t_first_name") String firstName, + @Column("t_last_name") String lastName, + @Column("t_email_address") String email, + @Column("t_locked_assignment") boolean lockedAssignment, + @Column("t_user_language") String userLanguage, + @Column("t_ext_reference") String extReference, + @Column("t_currency_code") String currencyCode, + @Column("t_tags") @Array List<String> ticketTags, + @Column("t_subscription_id") UUID ticketSubscriptionId, + @Column("t_vat_status") PriceContainer.VatStatus ticketVatStatus, + // + @Column("tr_id") String trId, + @Column("tr_validity") Date trValidity, + @Column("tr_status") TicketReservation.TicketReservationStatus trStatus, + @Column("tr_full_name") String trFullName, + @Column("tr_first_name") String trFirstName, + @Column("tr_last_name") String trLastName, + @Column("tr_email_address") String trEmail, + @Column("tr_billing_address") String trBillingAddress, + @Column("tr_confirmation_ts") ZonedDateTime trConfirmationTimestamp, + @Column("tr_latest_reminder_ts") ZonedDateTime trLatestReminder, + @Column("tr_payment_method") PaymentProxy trPaymentMethod, + @Column("tr_offline_payment_reminder_sent") Boolean trReminderSent, + @Column("tr_promo_code_id_fk") Integer trPromoCodeDiscountId, + @Column("tr_automatic") boolean trAutomatic, + @Column("tr_user_language") String resUserLanguage, + @Column("tr_direct_assignment") boolean directAssignment, + @Column("tr_invoice_number") String invoiceNumber, + @Column("tr_invoice_model") String invoiceModel, + @Column("tr_vat_status") PriceContainer.VatStatus reservationVatStatus, + @Column("tr_vat_nr") String vatNr, + @Column("tr_vat_country") String vatCountry, + @Column("tr_invoice_requested") boolean invoiceRequested, + @Column("tr_used_vat_percent") BigDecimal usedVatPercent, + @Column("tr_vat_included") Boolean vatIncluded, + @Column("tr_creation_ts") ZonedDateTime reservationCreationTimestamp, + @Column("tr_customer_reference") String customerReference, + @Column("tr_registration_ts") ZonedDateTime reservationRegistrationTimestamp, + // + @Column("tr_billing_address_company") String billingAddressCompany, + @Column("tr_billing_address_line1") String billingAddressLine1, + @Column("tr_billing_address_line2") String billingAddressLine2, + @Column("tr_billing_address_city") String billingAddressCity, + @Column("tr_billing_address_state") String billingAddressState, + @Column("tr_billing_address_zip") String billingAddressZip, + @Column("tr_invoicing_additional_information") @JSONData TicketReservationInvoicingAdditionalInfo invoicingAdditionalInfo, + + @Column("tr_src_price_cts") int reservationSrcPriceCts, + @Column("tr_final_price_cts") int reservationFinalPriceCts, + @Column("tr_vat_cts") int reservationVatCts, + @Column("tr_discount_cts") int reservationDiscountCts, + @Column("tr_currency_code") String reservationCurrencyCode, + // + @Column("tc_id") int tcId, + @Column("tc_inception") ZonedDateTime tcUtcInception, + @Column("tc_expiration") ZonedDateTime tcUtcExpiration, + @Column("tc_max_tickets") int tcMaxTickets, + @Column("tc_name") String tcName, + @Column("tc_src_price_cts") int tcSrcPriceCts, + @Column("tc_access_restricted") boolean tcAccessRestricted, + @Column("tc_tc_status") TicketCategory.Status tcStatus, + @Column("tc_event_id") int tcEventId, + @Column("tc_bounded") boolean bounded, + @Column("tc_category_code") String code, + @Column("tc_valid_checkin_from") ZonedDateTime validCheckInFrom, + @Column("tc_valid_checkin_to") ZonedDateTime validCheckInTo, + @Column("tc_ticket_validity_start") ZonedDateTime ticketValidityStart, + @Column("tc_ticket_validity_end") ZonedDateTime ticketValidityEnd, + @Column("tc_ordinal") int ordinal, + @Column("tc_ticket_checkin_strategy") TicketCategory.TicketCheckInStrategy ticketCheckInStrategy, + @Column("tc_metadata") @JSONData AlfioMetadata categoryMetadata, + @Column("tc_ticket_access_type") TicketCategory.TicketAccessType ticketAccessType, + + @Column("e_format") Event.EventFormat eventFormat, + @Column("e_short_name") String eventShortName, + @Column("e_display_name") String eventDisplayName, + @Column("e_start_ts") ZonedDateTime eventStartTs, + @Column("e_end_ts") ZonedDateTime eventEndTs, + @Column("e_time_zone") String timezone, + @Column("e_private_key") String eventPrivateKey, + @Column("e_org_id") int eventOrgId, + @Column("e_metadata") @JSONData AlfioMetadata eventMetadata, + @Column("e_locales") int locales, + @Column("e_version") String eventVersion, + + @Column("tai_additional_info") @JSONData Map<String, List<String>> ticketAdditionalInfo + ) { + + this.ticket = new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, + lockedAssignment, userLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference, currencyCode, + ticketTags, ticketSubscriptionId, ticketVatStatus); + this.ticketReservation = new TicketReservation(trId, trValidity, trStatus, trFullName, trFirstName, trLastName, trEmail, trBillingAddress, + trConfirmationTimestamp, trLatestReminder, trPaymentMethod, trReminderSent, trPromoCodeDiscountId, trAutomatic, resUserLanguage, + directAssignment, invoiceNumber, invoiceModel, reservationVatStatus, vatNr, vatCountry, invoiceRequested, usedVatPercent, vatIncluded, reservationCreationTimestamp, customerReference, + reservationRegistrationTimestamp, reservationSrcPriceCts, reservationFinalPriceCts, reservationVatCts, reservationDiscountCts, reservationCurrencyCode); + this.ticketCategory = new TicketCategory(tcId, tcUtcInception, tcUtcExpiration, tcMaxTickets, tcName, + tcAccessRestricted, tcStatus, tcEventId, bounded, tcSrcPriceCts, code, validCheckInFrom, validCheckInTo, + ticketValidityStart, ticketValidityEnd, currencyCode, ordinal, ticketCheckInStrategy, ticketAccessType); + + this.categoryMetadata = categoryMetadata; + this.billingDetails = new BillingDetails(billingAddressCompany, billingAddressLine1, billingAddressLine2, billingAddressZip, billingAddressCity, billingAddressState, vatCountry, vatNr, invoicingAdditionalInfo); + this.eventWithCheckInInfo = new EventWithCheckInInfo(eventId, eventFormat, eventShortName, eventDisplayName, eventStartTs, eventEndTs, timezone, eventPrivateKey, eventOrgId, eventMetadata, locales, eventVersion); + this.ticketAdditionalInfo = ticketAdditionalInfo; + } +} diff --git a/src/main/java/alfio/model/checkin/EventWithCheckInInfo.java b/src/main/java/alfio/model/checkin/EventWithCheckInInfo.java index 48d1b30ffa..fa5f4f793d 100644 --- a/src/main/java/alfio/model/checkin/EventWithCheckInInfo.java +++ b/src/main/java/alfio/model/checkin/EventWithCheckInInfo.java @@ -19,6 +19,7 @@ import alfio.model.*; import alfio.model.metadata.AlfioMetadata; import alfio.model.support.JSONData; +import alfio.util.EventUtil; import lombok.Getter; import org.apache.commons.lang3.tuple.Pair; @@ -41,6 +42,7 @@ public class EventWithCheckInInfo extends EventAndOrganizationId implements Even private final String privateKey; private final AlfioMetadata metadata; private final List<ContentLanguage> contentLanguages; + private final String version; public EventWithCheckInInfo(@Column("id") int id, @@ -53,7 +55,8 @@ public EventWithCheckInInfo(@Column("id") int id, @Column("private_key") String privateKey, @Column("org_id") int organizationId, @Column("metadata") @JSONData AlfioMetadata metadata, - @Column("locales") int locales) { + @Column("locales") int locales, + @Column("version") String version) { super(id, organizationId); this.zoneId = ZoneId.of(timezone); this.format = format; @@ -64,6 +67,7 @@ public EventWithCheckInInfo(@Column("id") int id, this.privateKey = privateKey; this.metadata = metadata; this.contentLanguages = ContentLanguage.findAllFor(locales); + this.version = version; } public boolean isOnline() { @@ -94,4 +98,9 @@ public BigDecimal getVat() { public List<ContentLanguage> getContentLanguages() { return contentLanguages; } + + @Override + public boolean supportsQRCodeCaseInsensitive() { + return EventUtil.supportsCaseInsensitiveQRCode(version); + } } diff --git a/src/main/java/alfio/model/checkin/OnlineCheckInFullInfo.java b/src/main/java/alfio/model/checkin/OnlineCheckInFullInfo.java deleted file mode 100644 index 1594eb8c90..0000000000 --- a/src/main/java/alfio/model/checkin/OnlineCheckInFullInfo.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * This file is part of alf.io. - * - * alf.io is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * alf.io is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with alf.io. If not, see <http://www.gnu.org/licenses/>. - */ -package alfio.model.checkin; - -import alfio.model.*; -import alfio.model.metadata.AlfioMetadata; -import alfio.model.support.Array; -import alfio.model.support.JSONData; -import alfio.model.transaction.PaymentProxy; -import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; - -import java.math.BigDecimal; -import java.time.ZonedDateTime; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -@Getter -public class OnlineCheckInFullInfo { - - private final Ticket ticket; - private final TicketReservation ticketReservation; - private final BillingDetails billingDetails; - private final TicketCategory ticketCategory; - private final AlfioMetadata categoryMetadata; - private final Map<String, List<String>> ticketAdditionalInfo; - private final EventWithCheckInInfo eventWithCheckInInfo; - - - public OnlineCheckInFullInfo(@Column("t_id") int id, - @Column("t_uuid") String uuid, - @Column("t_creation") ZonedDateTime creation, - @Column("t_category_id") int categoryId, - @Column("t_status") String status, - @Column("t_event_id") int eventId, - @Column("t_src_price_cts") int ticketSrcPriceCts, - @Column("t_final_price_cts") int ticketFinalPriceCts, - @Column("t_vat_cts") int ticketVatCts, - @Column("t_discount_cts") int ticketDiscountCts, - @Column("t_tickets_reservation_id") String ticketsReservationId, - @Column("t_full_name") String fullName, - @Column("t_first_name") String firstName, - @Column("t_last_name") String lastName, - @Column("t_email_address") String email, - @Column("t_locked_assignment") boolean lockedAssignment, - @Column("t_user_language") String userLanguage, - @Column("t_ext_reference") String extReference, - @Column("t_currency_code") String currencyCode, - @Column("t_tags") @Array List<String> ticketTags, - @Column("t_subscription_id") UUID ticketSubscriptionId, - @Column("t_vat_status") PriceContainer.VatStatus ticketVatStatus, - // - @Column("tr_id") String trId, - @Column("tr_validity") Date trValidity, - @Column("tr_status") TicketReservation.TicketReservationStatus trStatus, - @Column("tr_full_name") String trFullName, - @Column("tr_first_name") String trFirstName, - @Column("tr_last_name") String trLastName, - @Column("tr_email_address") String trEmail, - @Column("tr_billing_address") String trBillingAddress, - @Column("tr_confirmation_ts") ZonedDateTime trConfirmationTimestamp, - @Column("tr_latest_reminder_ts") ZonedDateTime trLatestReminder, - @Column("tr_payment_method") PaymentProxy trPaymentMethod, - @Column("tr_offline_payment_reminder_sent") Boolean trReminderSent, - @Column("tr_promo_code_id_fk") Integer trPromoCodeDiscountId, - @Column("tr_automatic") boolean trAutomatic, - @Column("tr_user_language") String resUserLanguage, - @Column("tr_direct_assignment") boolean directAssignment, - @Column("tr_invoice_number") String invoiceNumber, - @Column("tr_invoice_model") String invoiceModel, - @Column("tr_vat_status") PriceContainer.VatStatus reservationVatStatus, - @Column("tr_vat_nr") String vatNr, - @Column("tr_vat_country") String vatCountry, - @Column("tr_invoice_requested") boolean invoiceRequested, - @Column("tr_used_vat_percent") BigDecimal usedVatPercent, - @Column("tr_vat_included") Boolean vatIncluded, - @Column("tr_creation_ts") ZonedDateTime reservationCreationTimestamp, - @Column("tr_customer_reference") String customerReference, - @Column("tr_registration_ts") ZonedDateTime reservationRegistrationTimestamp, - // - @Column("tr_billing_address_company") String billingAddressCompany, - @Column("tr_billing_address_line1") String billingAddressLine1, - @Column("tr_billing_address_line2") String billingAddressLine2, - @Column("tr_billing_address_city") String billingAddressCity, - @Column("tr_billing_address_state") String billingAddressState, - @Column("tr_billing_address_zip") String billingAddressZip, - @Column("tr_invoicing_additional_information") @JSONData TicketReservationInvoicingAdditionalInfo invoicingAdditionalInfo, - - @Column("tr_src_price_cts") int reservationSrcPriceCts, - @Column("tr_final_price_cts") int reservationFinalPriceCts, - @Column("tr_vat_cts") int reservationVatCts, - @Column("tr_discount_cts") int reservationDiscountCts, - @Column("tr_currency_code") String reservationCurrencyCode, - // - @Column("tc_id") int tcId, - @Column("tc_inception") ZonedDateTime tcUtcInception, - @Column("tc_expiration") ZonedDateTime tcUtcExpiration, - @Column("tc_max_tickets") int tcMaxTickets, - @Column("tc_name") String tcName, - @Column("tc_src_price_cts") int tcSrcPriceCts, - @Column("tc_access_restricted") boolean tcAccessRestricted, - @Column("tc_tc_status") TicketCategory.Status tcStatus, - @Column("tc_event_id") int tcEventId, - @Column("tc_bounded") boolean bounded, - @Column("tc_category_code") String code, - @Column("tc_valid_checkin_from") ZonedDateTime validCheckInFrom, - @Column("tc_valid_checkin_to") ZonedDateTime validCheckInTo, - @Column("tc_ticket_validity_start") ZonedDateTime ticketValidityStart, - @Column("tc_ticket_validity_end") ZonedDateTime ticketValidityEnd, - @Column("tc_ordinal") int ordinal, - @Column("tc_ticket_checkin_strategy") TicketCategory.TicketCheckInStrategy ticketCheckInStrategy, - @Column("tc_metadata") @JSONData AlfioMetadata categoryMetadata, - @Column("tc_ticket_access_type") TicketCategory.TicketAccessType ticketAccessType, - - @Column("e_format") Event.EventFormat eventFormat, - @Column("e_short_name") String eventShortName, - @Column("e_display_name") String eventDisplayName, - @Column("e_start_ts") ZonedDateTime eventStartTs, - @Column("e_end_ts") ZonedDateTime eventEndTs, - @Column("e_time_zone") String timezone, - @Column("e_private_key") String eventPrivateKey, - @Column("e_org_id") int eventOrgId, - @Column("e_metadata") @JSONData AlfioMetadata eventMetadata, - @Column("e_locales") int locales, - - @Column("tai_additional_info") @JSONData Map<String, List<String>> ticketAdditionalInfo - ) { - - this.ticket = new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, - lockedAssignment, userLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference, currencyCode, - ticketTags, ticketSubscriptionId, ticketVatStatus); - this.ticketReservation = new TicketReservation(trId, trValidity, trStatus, trFullName, trFirstName, trLastName, trEmail, trBillingAddress, - trConfirmationTimestamp, trLatestReminder, trPaymentMethod, trReminderSent, trPromoCodeDiscountId, trAutomatic, resUserLanguage, - directAssignment, invoiceNumber, invoiceModel, reservationVatStatus, vatNr, vatCountry, invoiceRequested, usedVatPercent, vatIncluded, reservationCreationTimestamp, customerReference, - reservationRegistrationTimestamp, reservationSrcPriceCts, reservationFinalPriceCts, reservationVatCts, reservationDiscountCts, reservationCurrencyCode); - this.ticketCategory = new TicketCategory(tcId, tcUtcInception, tcUtcExpiration, tcMaxTickets, tcName, - tcAccessRestricted, tcStatus, tcEventId, bounded, tcSrcPriceCts, code, validCheckInFrom, validCheckInTo, - ticketValidityStart, ticketValidityEnd, currencyCode, ordinal, ticketCheckInStrategy, ticketAccessType); - - this.categoryMetadata = categoryMetadata; - this.billingDetails = new BillingDetails(billingAddressCompany, billingAddressLine1, billingAddressLine2, billingAddressZip, billingAddressCity, billingAddressState, vatCountry, vatNr, invoicingAdditionalInfo); - this.eventWithCheckInInfo = new EventWithCheckInInfo(eventId, eventFormat, eventShortName, eventDisplayName, eventStartTs, eventEndTs, timezone, eventPrivateKey, eventOrgId, eventMetadata, locales); - this.ticketAdditionalInfo = ticketAdditionalInfo; - } -} diff --git a/src/main/java/alfio/model/decorator/AdditionalServiceItemPriceContainer.java b/src/main/java/alfio/model/decorator/AdditionalServiceItemPriceContainer.java index cf4c8d774d..e7e5254fa8 100644 --- a/src/main/java/alfio/model/decorator/AdditionalServiceItemPriceContainer.java +++ b/src/main/java/alfio/model/decorator/AdditionalServiceItemPriceContainer.java @@ -18,14 +18,12 @@ import alfio.model.*; import alfio.util.MonetaryUtil; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; import java.math.BigDecimal; import java.util.Optional; -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public class AdditionalServiceItemPriceContainer implements SummaryPriceContainer { @Delegate(excludes = PriceContainer.class) private final AdditionalServiceItem additionalServiceItem; @@ -35,6 +33,20 @@ public class AdditionalServiceItemPriceContainer implements SummaryPriceContaine private final VatStatus eventVatStatus; private final BigDecimal eventVatPercentage; + private AdditionalServiceItemPriceContainer(AdditionalServiceItem additionalServiceItem, + AdditionalService additionalService, + String currencyCode, + PromoCodeDiscount discount, + VatStatus eventVatStatus, + BigDecimal eventVatPercentage) { + this.additionalServiceItem = additionalServiceItem; + this.additionalService = additionalService; + this.currencyCode = currencyCode; + this.discount = discount; + this.eventVatStatus = eventVatStatus; + this.eventVatPercentage = eventVatPercentage; + } + @Override public int getSrcPriceCts() { return Optional.ofNullable(additionalServiceItem.getSrcPriceCts()).orElse(0); diff --git a/src/main/java/alfio/model/decorator/AdditionalServicePriceContainer.java b/src/main/java/alfio/model/decorator/AdditionalServicePriceContainer.java index 2f6d31902a..efeb0af13b 100644 --- a/src/main/java/alfio/model/decorator/AdditionalServicePriceContainer.java +++ b/src/main/java/alfio/model/decorator/AdditionalServicePriceContainer.java @@ -21,13 +21,10 @@ import alfio.model.PriceContainer; import alfio.model.PromoCodeDiscount; import alfio.util.MonetaryUtil; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import java.math.BigDecimal; import java.util.Optional; -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class AdditionalServicePriceContainer implements PriceContainer { private final BigDecimal customAmount; @@ -37,6 +34,20 @@ public class AdditionalServicePriceContainer implements PriceContainer { private final BigDecimal vatPercentage; private final VatStatus vatStatus; + private AdditionalServicePriceContainer(BigDecimal customAmount, + AdditionalService additionalService, + PromoCodeDiscount promoCodeDiscount, + String currencyCode, + BigDecimal vatPercentage, + VatStatus vatStatus) { + this.customAmount = customAmount; + this.additionalService = additionalService; + this.promoCodeDiscount = promoCodeDiscount; + this.currencyCode = currencyCode; + this.vatPercentage = vatPercentage; + this.vatStatus = vatStatus; + } + @Override public int getSrcPriceCts() { if(additionalService.isFixPrice()) { diff --git a/src/main/java/alfio/model/decorator/TicketPriceContainer.java b/src/main/java/alfio/model/decorator/TicketPriceContainer.java index 50a5750362..3d76ebce6e 100644 --- a/src/main/java/alfio/model/decorator/TicketPriceContainer.java +++ b/src/main/java/alfio/model/decorator/TicketPriceContainer.java @@ -19,17 +19,12 @@ import alfio.model.PromoCodeDiscount; import alfio.model.SummaryPriceContainer; import alfio.model.Ticket; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; import org.apache.commons.lang3.ObjectUtils; import java.math.BigDecimal; import java.util.Optional; -import static alfio.util.MonetaryUtil.unitToCents; - -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class TicketPriceContainer implements SummaryPriceContainer { @Delegate(excludes = OverridePriceContainer.class) @@ -38,6 +33,16 @@ public class TicketPriceContainer implements SummaryPriceContainer { private final BigDecimal vatPercentage; private final VatStatus vatStatus; + private TicketPriceContainer(Ticket ticket, + PromoCodeDiscount promoCodeDiscount, + BigDecimal vatPercentage, + VatStatus vatStatus) { + this.ticket = ticket; + this.promoCodeDiscount = promoCodeDiscount; + this.vatPercentage = vatPercentage; + this.vatStatus = vatStatus; + } + @Override public Optional<PromoCodeDiscount> getDiscount() { return Optional.ofNullable(promoCodeDiscount) @@ -68,7 +73,9 @@ public static TicketPriceContainer from(Ticket t, VatStatus reservationVatStatus @Override public BigDecimal getTaxablePrice() { - if(vatStatus != VatStatus.INCLUDED_EXEMPT && vatStatus != VatStatus.NOT_INCLUDED_EXEMPT) { + if(vatStatus != VatStatus.INCLUDED_EXEMPT + && vatStatus != VatStatus.NOT_INCLUDED_EXEMPT + && vatStatus != VatStatus.CUSTOM_NOT_INCLUDED_EXEMPT) { return SummaryPriceContainer.super.getTaxablePrice(); } return BigDecimal.ZERO; diff --git a/src/main/java/alfio/model/extension/CreditNoteGeneration.java b/src/main/java/alfio/model/extension/CreditNoteGeneration.java index 56ff48bb65..e17506328a 100644 --- a/src/main/java/alfio/model/extension/CreditNoteGeneration.java +++ b/src/main/java/alfio/model/extension/CreditNoteGeneration.java @@ -17,9 +17,14 @@ package alfio.model.extension; -import lombok.Data; - -@Data public class CreditNoteGeneration { private String creditNoteNumber; + + public String getCreditNoteNumber() { + return creditNoteNumber; + } + + public void setCreditNoteNumber(String creditNoteNumber) { + this.creditNoteNumber = creditNoteNumber; + } } diff --git a/src/main/java/alfio/model/extension/CustomTaxPolicy.java b/src/main/java/alfio/model/extension/CustomTaxPolicy.java new file mode 100644 index 0000000000..955baec5fc --- /dev/null +++ b/src/main/java/alfio/model/extension/CustomTaxPolicy.java @@ -0,0 +1,78 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.extension; + +import alfio.model.PriceContainer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class CustomTaxPolicy { + + private final Set<TicketTaxPolicy> ticketPolicies; + + @JsonCreator + public CustomTaxPolicy(@JsonProperty("ticketPolicies") List<TicketTaxPolicy> ticketPolicies) { + this.ticketPolicies = new HashSet<>(ticketPolicies); + } + + public Set<TicketTaxPolicy> getTicketPolicies() { + return ticketPolicies; + } + + public static class TicketTaxPolicy implements Comparable<TicketTaxPolicy> { + private final String uuid; + private final PriceContainer.VatStatus taxPolicy; + + @JsonCreator + public TicketTaxPolicy(@JsonProperty("uuid") String uuid, + @JsonProperty("taxPolicy") PriceContainer.VatStatus taxPolicy) { + this.uuid = Objects.requireNonNull(uuid); + this.taxPolicy = taxPolicy; + } + + public PriceContainer.VatStatus getTaxPolicy() { + return taxPolicy; + } + + public String getUuid() { + return uuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TicketTaxPolicy)) return false; + TicketTaxPolicy that = (TicketTaxPolicy) o; + return Objects.equals(this.uuid, that.uuid); + } + + @Override + public int hashCode() { + return Objects.hash(uuid); + } + + @Override + public int compareTo(TicketTaxPolicy o) { + return this.equals(o) ? 0 : this.uuid.compareTo(o.uuid); + } + } +} diff --git a/src/main/java/alfio/model/extension/DynamicDiscount.java b/src/main/java/alfio/model/extension/DynamicDiscount.java index 199fe46c75..41809aab33 100644 --- a/src/main/java/alfio/model/extension/DynamicDiscount.java +++ b/src/main/java/alfio/model/extension/DynamicDiscount.java @@ -17,17 +17,38 @@ package alfio.model.extension; import alfio.model.PromoCodeDiscount; -import lombok.Data; import java.util.Arrays; -@Data public class DynamicDiscount { private String amount; private String type; private String code; + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setAmount(String amount) { + this.amount = amount; + } + + public String getAmount() { + return amount; + } public PromoCodeDiscount.DiscountType getDiscountType() { return Arrays.stream(PromoCodeDiscount.DiscountType.values()) diff --git a/src/main/java/alfio/model/extension/InvoiceGeneration.java b/src/main/java/alfio/model/extension/InvoiceGeneration.java index ae2ca1a46d..9fa4ed018d 100644 --- a/src/main/java/alfio/model/extension/InvoiceGeneration.java +++ b/src/main/java/alfio/model/extension/InvoiceGeneration.java @@ -17,9 +17,14 @@ package alfio.model.extension; -import lombok.Data; - -@Data public class InvoiceGeneration { private String invoiceNumber; + + public String getInvoiceNumber() { + return invoiceNumber; + } + + public void setInvoiceNumber(String invoiceNumber) { + this.invoiceNumber = invoiceNumber; + } } diff --git a/src/main/java/alfio/model/extension/PdfGenerationResult.java b/src/main/java/alfio/model/extension/PdfGenerationResult.java index f3b8e628a7..d8680ea23c 100644 --- a/src/main/java/alfio/model/extension/PdfGenerationResult.java +++ b/src/main/java/alfio/model/extension/PdfGenerationResult.java @@ -16,12 +16,7 @@ */ package alfio.model.extension; -import lombok.Data; - -@Data -public class PdfGenerationResult { - private final String tempFilePath; - +public record PdfGenerationResult(String tempFilePath) { public boolean isEmpty() { return tempFilePath == null || tempFilePath.isEmpty(); } diff --git a/src/main/java/alfio/model/metadata/AlfioMetadata.java b/src/main/java/alfio/model/metadata/AlfioMetadata.java index 4e17d5ec69..30d568bf33 100644 --- a/src/main/java/alfio/model/metadata/AlfioMetadata.java +++ b/src/main/java/alfio/model/metadata/AlfioMetadata.java @@ -25,21 +25,39 @@ @Getter public class AlfioMetadata { + private static final AlfioMetadata EMPTY = new AlfioMetadata(null, Map.of(), List.of(), null); private final OnlineConfiguration onlineConfiguration; // list of requirements for participants, e.g. software private final Map<String, String> requirementsDescriptions; private final List<ConditionsLink> conditionsToBeAccepted; + private final String copiedFrom; @JsonCreator public AlfioMetadata(@JsonProperty("onlineConfiguration") OnlineConfiguration onlineConfiguration, @JsonProperty("requirementsDescriptions") Map<String, String> requirementsDescriptions, - @JsonProperty("conditionsToBeAccepted") List<ConditionsLink> conditionsToBeAccepted) { + @JsonProperty("conditionsToBeAccepted") List<ConditionsLink> conditionsToBeAccepted, + @JsonProperty("copiedFrom") String copiedFrom) { this.onlineConfiguration = onlineConfiguration; this.requirementsDescriptions = requirementsDescriptions; this.conditionsToBeAccepted = conditionsToBeAccepted; + this.copiedFrom = copiedFrom; } public static AlfioMetadata empty() { - return new AlfioMetadata(null, Map.of(), List.of()); + return EMPTY; + } + + /** + * Returns a merged version of the metadata. + * The "copiedFrom" attribute will not be overridden by {@code other} + * @param other metadata to merge + * @return merged metadata + */ + public AlfioMetadata merge(AlfioMetadata other) { + return new AlfioMetadata( + other.onlineConfiguration, + other.requirementsDescriptions, + other.conditionsToBeAccepted, + copiedFrom); } } diff --git a/src/main/java/alfio/model/metadata/ConditionsLink.java b/src/main/java/alfio/model/metadata/ConditionsLink.java index 9fbcfbe243..3e0ded5ef6 100644 --- a/src/main/java/alfio/model/metadata/ConditionsLink.java +++ b/src/main/java/alfio/model/metadata/ConditionsLink.java @@ -25,7 +25,7 @@ @Getter public class ConditionsLink { enum Type { - TERMS_OF_PARTICIPATION, PRIVACY_POLICY, CUSTOM; + TERMS_OF_PARTICIPATION, PRIVACY_POLICY, CUSTOM } private final Type type; diff --git a/src/main/java/alfio/model/metadata/JoinLink.java b/src/main/java/alfio/model/metadata/JoinLink.java index dd88ecf23c..b4348973d6 100644 --- a/src/main/java/alfio/model/metadata/JoinLink.java +++ b/src/main/java/alfio/model/metadata/JoinLink.java @@ -18,31 +18,15 @@ import alfio.model.LocalizedContent; import alfio.util.EventUtil; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; import java.time.LocalDateTime; import java.util.Map; -import java.util.Objects; -@Getter -public class JoinLink { - private final String link; - private final LocalDateTime validFrom; - private final LocalDateTime validTo; - private final Map<String, String> linkText; - - @JsonCreator - public JoinLink(@JsonProperty("link") String link, - @JsonProperty("validFrom") LocalDateTime validFrom, - @JsonProperty("validTo") LocalDateTime validTo, - @JsonProperty("linkText") Map<String, String> linkText) { - this.link = link; - this.validFrom = validFrom; - this.validTo = validTo; - this.linkText = Objects.requireNonNullElse(linkText, Map.of()); - } +public record JoinLink(@JsonProperty("link") String link, + @JsonProperty("validFrom") LocalDateTime validFrom, + @JsonProperty("validTo") LocalDateTime validTo, + @JsonProperty("linkText") Map<String, String> linkText) { public boolean hasLinkText() { return !linkText.isEmpty(); @@ -51,24 +35,4 @@ public boolean hasLinkText() { public String getLocalizedText(String lang, LocalizedContent fallback) { return EventUtil.getLocalizedMessage(linkText, lang, fallback); } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JoinLink joinLink = (JoinLink) o; - return Objects.equals(link, joinLink.link) - && Objects.equals(validFrom, joinLink.validFrom) - && Objects.equals(validTo, joinLink.validTo) - && linkText.equals(joinLink.linkText); - } - - @Override - public int hashCode() { - return Objects.hash(link, validFrom, validTo, linkText); - } } diff --git a/src/main/java/alfio/model/metadata/SubscriptionMetadata.java b/src/main/java/alfio/model/metadata/SubscriptionMetadata.java new file mode 100644 index 0000000000..ef3fc9007b --- /dev/null +++ b/src/main/java/alfio/model/metadata/SubscriptionMetadata.java @@ -0,0 +1,50 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.metadata; + +import alfio.model.api.v1.admin.subscription.SubscriptionConfiguration; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; +import java.util.Objects; + +public class SubscriptionMetadata { + + private final Map<String, String> properties; + private final SubscriptionConfiguration configuration; + + @JsonCreator + public SubscriptionMetadata(@JsonProperty("properties") Map<String, String> properties, + @JsonProperty("configuration") SubscriptionConfiguration configuration) { + this.properties = Objects.requireNonNullElse(properties, Map.of()); + this.configuration = Objects.requireNonNullElseGet(configuration, SubscriptionConfiguration::defaultConfiguration); + } + + + public Map<String, String> getProperties() { + return properties; + } + + public SubscriptionConfiguration getConfiguration() { + return configuration; + } + + public static SubscriptionMetadata empty() { + return new SubscriptionMetadata(null, null); + } +} diff --git a/src/main/java/alfio/model/metadata/TicketMetadata.java b/src/main/java/alfio/model/metadata/TicketMetadata.java index 8425c46848..e54f271970 100644 --- a/src/main/java/alfio/model/metadata/TicketMetadata.java +++ b/src/main/java/alfio/model/metadata/TicketMetadata.java @@ -80,6 +80,9 @@ public int hashCode() { public static TicketMetadata empty() { return new TicketMetadata(null, null, Map.of()); } + public TicketMetadata withAttributes(Map<String, String> attributes) { + return new TicketMetadata(joinLink, Map.copyOf(linkDescription), Map.copyOf(attributes)); + } public static TicketMetadata copyOf(TicketMetadata src) { if (src != null) { diff --git a/src/main/java/alfio/model/modification/AttendeeData.java b/src/main/java/alfio/model/modification/AttendeeData.java new file mode 100644 index 0000000000..8fcd833589 --- /dev/null +++ b/src/main/java/alfio/model/modification/AttendeeData.java @@ -0,0 +1,71 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.modification; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; + +public class AttendeeData { + private final String firstName; + private final String lastName; + private final String email; + private final Map<String, String> metadata; + + @JsonCreator + public AttendeeData(@JsonProperty("firstName") String firstName, + @JsonProperty("lastName") String lastName, + @JsonProperty("email") String email, + @JsonProperty("metadata") Map<String, String> metadata) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.metadata = metadata; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getEmail() { + return email; + } + + public Map<String, String> getMetadata() { + return metadata; + } + + public boolean hasMetadata() { + return metadata != null; + } + + public boolean hasContactData() { + return StringUtils.isNotBlank(firstName) + || StringUtils.isNotBlank(lastName) + || StringUtils.isNotBlank(email); + } + + public static AttendeeData empty() { + return new AttendeeData(null, null, null, null); + } +} diff --git a/src/main/java/alfio/model/modification/EventModification.java b/src/main/java/alfio/model/modification/EventModification.java index b85949395f..de6bb1dfe5 100644 --- a/src/main/java/alfio/model/modification/EventModification.java +++ b/src/main/java/alfio/model/modification/EventModification.java @@ -390,7 +390,7 @@ public static class Builder { private final alfio.model.AdditionalService src; private ZoneId zoneId; - private List<AdditionalField> additionalServiceFields = new ArrayList<>(); + private final List<AdditionalField> additionalServiceFields = new ArrayList<>(); private List<AdditionalServiceText> title = new ArrayList<>(); private List<AdditionalServiceText> description = new ArrayList<>(); private PriceContainer priceContainer; @@ -448,7 +448,7 @@ public AdditionalServiceText(@JsonProperty("id") Integer id, } public static AdditionalServiceText from(alfio.model.AdditionalServiceText src) { - return new AdditionalServiceText(src.getId(), src.getLocale(), src.getValue(), src.getType()); + return new AdditionalServiceText(src.id(), src.locale(), src.value(), src.type()); } } } diff --git a/src/main/java/alfio/model/modification/GroupMemberModification.java b/src/main/java/alfio/model/modification/GroupMemberModification.java index ee87a00b43..9085ae1a3d 100644 --- a/src/main/java/alfio/model/modification/GroupMemberModification.java +++ b/src/main/java/alfio/model/modification/GroupMemberModification.java @@ -43,12 +43,10 @@ public GroupMemberModification(@JsonProperty("id") Integer id, public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof GroupMemberModification)) { + if (!(o instanceof GroupMemberModification that)) { return false; } - GroupMemberModification that = (GroupMemberModification) o; - return new EqualsBuilder() .append(id, that.id) .append(value, that.value) diff --git a/src/main/java/alfio/model/modification/MetadataModification.java b/src/main/java/alfio/model/modification/MetadataModification.java index e24de54909..1d353821c1 100644 --- a/src/main/java/alfio/model/modification/MetadataModification.java +++ b/src/main/java/alfio/model/modification/MetadataModification.java @@ -48,7 +48,8 @@ public AlfioMetadata toMetadataObj() { return new AlfioMetadata( new OnlineConfiguration(callLinks.stream().map(CallLinkModification::toCallLink).collect(Collectors.toList())), requirementsDescriptions, - List.of() + List.of(), + null ); } diff --git a/src/main/java/alfio/model/modification/OrganizationModification.java b/src/main/java/alfio/model/modification/OrganizationModification.java index b229e46429..cadf98aad7 100644 --- a/src/main/java/alfio/model/modification/OrganizationModification.java +++ b/src/main/java/alfio/model/modification/OrganizationModification.java @@ -17,8 +17,10 @@ package alfio.model.modification; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import org.apache.commons.lang3.StringUtils; @Getter public class OrganizationModification { @@ -44,4 +46,12 @@ public OrganizationModification(@JsonProperty("id") Integer id, this.externalId = externalId; this.slug = slug; } + + @JsonIgnore + public boolean isValid(boolean create) { + return (create || (id != null && id > 0)) + && StringUtils.isNotEmpty(name) + && StringUtils.isNotEmpty(email) + && StringUtils.isNotEmpty(description); + } } diff --git a/src/main/java/alfio/model/modification/PollModification.java b/src/main/java/alfio/model/modification/PollModification.java index d94007420e..34c60f00eb 100644 --- a/src/main/java/alfio/model/modification/PollModification.java +++ b/src/main/java/alfio/model/modification/PollModification.java @@ -66,23 +66,23 @@ public boolean isValid(boolean update) { } public static PollModification from(Poll poll) { - return new PollModification(poll.getId(), - poll.getTitle(), - poll.getDescription(), - poll.getOrder(), + return new PollModification(poll.id(), + poll.title(), + poll.description(), + poll.order(), List.of(), - !poll.getAllowedTags().isEmpty(), - poll.getStatus()); + !poll.allowedTags().isEmpty(), + poll.status()); } public static PollModification from(PollWithOptions pollWithOptions) { - var poll = pollWithOptions.getPoll(); - return new PollModification(poll.getId(), - poll.getTitle(), - poll.getDescription(), - poll.getOrder(), - pollWithOptions.getOptions().stream().map(PollOptionModification::from).collect(Collectors.toList()), - !poll.getAllowedTags().isEmpty(), - poll.getStatus()); + var poll = pollWithOptions.poll(); + return new PollModification(poll.id(), + poll.title(), + poll.description(), + poll.order(), + pollWithOptions.options().stream().map(PollOptionModification::from).collect(Collectors.toList()), + !poll.allowedTags().isEmpty(), + poll.status()); } } diff --git a/src/main/java/alfio/model/modification/PollOptionModification.java b/src/main/java/alfio/model/modification/PollOptionModification.java index 7aee8b2762..cbd58eeb43 100644 --- a/src/main/java/alfio/model/modification/PollOptionModification.java +++ b/src/main/java/alfio/model/modification/PollOptionModification.java @@ -45,6 +45,6 @@ public boolean isValid(boolean update) { } public static PollOptionModification from(PollOption option) { - return new PollOptionModification(option.getId(), option.getTitle(), option.getDescription()); + return new PollOptionModification(option.id(), option.title(), option.description()); } } diff --git a/src/main/java/alfio/model/modification/PromoCodeDiscountModification.java b/src/main/java/alfio/model/modification/PromoCodeDiscountModification.java index 9f5c60ca5e..a1e285c7e0 100644 --- a/src/main/java/alfio/model/modification/PromoCodeDiscountModification.java +++ b/src/main/java/alfio/model/modification/PromoCodeDiscountModification.java @@ -36,6 +36,7 @@ public class PromoCodeDiscountModification { private final Integer organizationId; private final Integer eventId; private final String promoCode; + private final String currencyCode; private final DateTimeModification start; private final DateTimeModification end; private final BigDecimal discountAmount; @@ -57,6 +58,7 @@ public PromoCodeDiscountModification( @JsonProperty("start") DateTimeModification start, @JsonProperty("end") DateTimeModification end, @JsonProperty("discountAmount") BigDecimal discountAmount, + @JsonProperty("currencyCode") String currencyCode, @JsonProperty("discountType") DiscountType discountType, @JsonProperty("categories") List<Integer> categories, @JsonProperty("utcOffset") Integer utcOffset, @@ -72,6 +74,7 @@ public PromoCodeDiscountModification( this.start = start; this.end = end; this.discountAmount = discountAmount; + this.currencyCode = currencyCode; this.discountType = discountType; this.categories = Optional.ofNullable(categories).map(l -> l.stream().filter(Objects::nonNull).collect(Collectors.toList())).orElse(Collections.emptyList()); this.utcOffset = utcOffset; @@ -90,7 +93,7 @@ private int getDiscountInCents(String currencyCode) { return MonetaryUtil.unitToCents(discountAmount, currencyCode); } - public int getDiscountValue(String currencyCode) { + public int getDiscountValue() { if(codeType != PromoCodeDiscount.CodeType.DISCOUNT) { return 0; } diff --git a/src/main/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmount.java b/src/main/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmount.java index d7d01a47d6..9db6e4f7b5 100644 --- a/src/main/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmount.java +++ b/src/main/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmount.java @@ -59,8 +59,9 @@ public String getFormattedEnd() { } public String getFormattedDiscountAmount() { - if(promo.getFixedAmount() && StringUtils.isNotBlank(eventCurrency)) { - return MonetaryUtil.formatCents(promo.getDiscountAmount(), eventCurrency); + var currency = StringUtils.firstNonEmpty(eventCurrency, promo.getCurrencyCode()); + if(promo.getFixedAmount() && StringUtils.isNotBlank(currency)) { + return MonetaryUtil.formatCents(promo.getDiscountAmount(), currency); } return null; } diff --git a/src/main/java/alfio/model/modification/ReservationRequest.java b/src/main/java/alfio/model/modification/ReservationRequest.java new file mode 100644 index 0000000000..1ae7610ff5 --- /dev/null +++ b/src/main/java/alfio/model/modification/ReservationRequest.java @@ -0,0 +1,43 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.modification; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static alfio.util.MiscUtils.getAtIndexOrNull; + +public interface ReservationRequest { + Integer getTicketCategoryId(); + Integer getQuantity(); + List<Map<String, String>> getMetadata(); + + default List<AttendeeData> getAttendees() { + return metadataToAttendeesList(getQuantity(), getMetadata()); + } + + static List<AttendeeData> metadataToAttendeesList(Integer quantity, List<Map<String, String>> metadata) { + int q = Objects.requireNonNullElse(quantity, 0); + return Stream.iterate(0, s -> s+1) + .limit(q) + .map(i -> new AttendeeData(null, null, null, getAtIndexOrNull(metadata, i))) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/alfio/model/modification/SendCodeModification.java b/src/main/java/alfio/model/modification/SendCodeModification.java index a8c10693d7..e402e842ef 100644 --- a/src/main/java/alfio/model/modification/SendCodeModification.java +++ b/src/main/java/alfio/model/modification/SendCodeModification.java @@ -18,11 +18,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.EqualsAndHashCode; import lombok.Getter; +import java.util.Objects; + @Getter -@EqualsAndHashCode public class SendCodeModification { private final String code; @@ -41,4 +41,16 @@ public SendCodeModification(@JsonProperty("code") String code, this.language = language; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SendCodeModification that = (SendCodeModification) o; + return Objects.equals(code, that.code) && Objects.equals(assignee, that.assignee) && Objects.equals(email, that.email) && Objects.equals(language, that.language); + } + + @Override + public int hashCode() { + return Objects.hash(code, assignee, email, language); + } } diff --git a/src/main/java/alfio/model/modification/SubscriptionDescriptorModification.java b/src/main/java/alfio/model/modification/SubscriptionDescriptorModification.java index c1c5e56385..f8490df5db 100644 --- a/src/main/java/alfio/model/modification/SubscriptionDescriptorModification.java +++ b/src/main/java/alfio/model/modification/SubscriptionDescriptorModification.java @@ -17,6 +17,8 @@ package alfio.model.modification; import alfio.model.PriceContainer.VatStatus; +import alfio.model.result.ErrorCode; +import alfio.model.result.Result; import alfio.model.subscription.SubscriptionDescriptor; import alfio.model.subscription.SubscriptionDescriptor.SubscriptionTimeUnit; import alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType; @@ -33,6 +35,10 @@ import java.util.Map; import java.util.UUID; +import static java.util.Objects.requireNonNullElse; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.length; + @Getter public class SubscriptionDescriptorModification { private final UUID id; @@ -89,8 +95,8 @@ public SubscriptionDescriptorModification(@JsonProperty("id") UUID id, @JsonProperty("timeZone") ZoneId timeZone, @JsonProperty("supportsTicketsGeneration") Boolean supportsTicketsGeneration) { this.id = id; - this.title = title; - this.description = description; + this.title = requireNonNullElse(title, Map.of()); + this.description = requireNonNullElse(description, Map.of()); this.maxAvailable = maxAvailable; this.onSaleFrom = onSaleFrom; this.onSaleTo = onSaleTo; @@ -110,7 +116,7 @@ public SubscriptionDescriptorModification(@JsonProperty("id") UUID id, this.termsAndConditionsUrl = termsAndConditionsUrl; this.privacyPolicyUrl = privacyPolicyUrl; this.fileBlobId = fileBlobId; - this.paymentProxies = paymentProxies; + this.paymentProxies = requireNonNullElse(paymentProxies, List.of()); this.timeZone = timeZone; this.supportsTicketsGeneration = supportsTicketsGeneration; } @@ -167,4 +173,23 @@ public static SubscriptionDescriptorModification fromModel(SubscriptionDescripto subscriptionDescriptor.getZoneId(), subscriptionDescriptor.isSupportsTicketsGeneration()); } + + public static Result<SubscriptionDescriptorModification> validate(SubscriptionDescriptorModification input) { + return new Result.Builder<SubscriptionDescriptorModification>() + .checkPrecondition(() -> !input.title.isEmpty(), ErrorCode.custom("title", "Title is mandatory")) + .checkPrecondition(() -> !input.description.isEmpty(), ErrorCode.custom("description", "Description is mandatory")) + .checkPrecondition(() -> input.title.keySet().equals(input.description.keySet()), ErrorCode.custom("locale-mismatch", "Mismatch between supported translations")) + .checkPrecondition(() -> isNotBlank(input.termsAndConditionsUrl), ErrorCode.custom("terms", "Terms and Conditions are mandatory")) + .checkPrecondition(() -> isNotBlank(input.fileBlobId), ErrorCode.custom("logo", "Logo is required")) + .checkPrecondition(() -> isNotBlank(input.currency) && length(input.currency) == 3, ErrorCode.custom("currency", "Currency Code is required")) + .checkPrecondition(() -> input.vatStatus != null, ErrorCode.custom("taxPolicy", "Tax Policy is required")) + .checkPrecondition(() -> input.vat != null, ErrorCode.custom("taxes", "Tax percentage is required")) + .checkPrecondition(() -> input.onSaleFrom != null, ErrorCode.custom("onSaleFrom", "'On Sale From' is required")) + .checkPrecondition(() -> input.timeZone != null, ErrorCode.custom("timeZone", "Time Zone is required")) + .checkPrecondition(() -> !input.paymentProxies.isEmpty(), ErrorCode.custom("paymentMethods", "At least one Payment Method is required")) + .checkPrecondition(() -> input.price != null, ErrorCode.custom("price", "Price is mandatory")) + .checkPrecondition(() -> input.usageType != null, ErrorCode.custom("usageType", "UsageType is mandatory")) + .checkPrecondition(() -> isNotBlank(input.fileBlobId), ErrorCode.custom("logo", "Logo is required")) + .build(() -> input); + } } diff --git a/src/main/java/alfio/model/modification/TicketFieldDescriptionModification.java b/src/main/java/alfio/model/modification/TicketFieldDescriptionModification.java index 11d126931a..cf89975f60 100644 --- a/src/main/java/alfio/model/modification/TicketFieldDescriptionModification.java +++ b/src/main/java/alfio/model/modification/TicketFieldDescriptionModification.java @@ -16,17 +16,28 @@ */ package alfio.model.modification; - -import lombok.Getter; -import lombok.Setter; - import java.util.Map; -@Getter -@Setter + public class TicketFieldDescriptionModification { private int ticketFieldConfigurationId; private String locale; private Map<String, Object> description; + + public int getTicketFieldConfigurationId() { + return ticketFieldConfigurationId; + } + + public void setTicketFieldConfigurationId(int ticketFieldConfigurationId) { + this.ticketFieldConfigurationId = ticketFieldConfigurationId; + } + + public String getLocale() { + return locale; + } + + public Map<String, Object> getDescription() { + return description; + } } diff --git a/src/main/java/alfio/model/modification/TicketReservationModification.java b/src/main/java/alfio/model/modification/TicketReservationModification.java index ff2afdf08e..e09698b444 100644 --- a/src/main/java/alfio/model/modification/TicketReservationModification.java +++ b/src/main/java/alfio/model/modification/TicketReservationModification.java @@ -23,7 +23,7 @@ import java.util.Map; @Data -public class TicketReservationModification implements Serializable { +public class TicketReservationModification implements ReservationRequest, Serializable { private Integer ticketCategoryId; private Integer quantity; private List<Map<String, String>> metadata; diff --git a/src/main/java/alfio/model/modification/TicketReservationWithOptionalCodeModification.java b/src/main/java/alfio/model/modification/TicketReservationWithOptionalCodeModification.java index c1a220f094..91b035d076 100644 --- a/src/main/java/alfio/model/modification/TicketReservationWithOptionalCodeModification.java +++ b/src/main/java/alfio/model/modification/TicketReservationWithOptionalCodeModification.java @@ -25,6 +25,6 @@ @Data public class TicketReservationWithOptionalCodeModification { @Delegate - private final TicketReservationModification ticketReservationModification; + private final ReservationRequest ticketReservationModification; private final Optional<SpecialPrice> specialPrice; } diff --git a/src/main/java/alfio/model/modification/TransactionMetadataModification.java b/src/main/java/alfio/model/modification/TransactionMetadataModification.java new file mode 100644 index 0000000000..09fcd23b2d --- /dev/null +++ b/src/main/java/alfio/model/modification/TransactionMetadataModification.java @@ -0,0 +1,40 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.modification; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TransactionMetadataModification { + private final DateTimeModification timestamp; + private final String notes; + + @JsonCreator + public TransactionMetadataModification(@JsonProperty("timestamp") DateTimeModification timestamp, + @JsonProperty("notes") String notes) { + this.timestamp = timestamp; + this.notes = notes; + } + + public DateTimeModification getTimestamp() { + return timestamp; + } + + public String getNotes() { + return notes; + } +} diff --git a/src/main/java/alfio/model/modification/UploadBase64FileModification.java b/src/main/java/alfio/model/modification/UploadBase64FileModification.java index 75f5654e69..b34df4c0db 100644 --- a/src/main/java/alfio/model/modification/UploadBase64FileModification.java +++ b/src/main/java/alfio/model/modification/UploadBase64FileModification.java @@ -16,15 +16,13 @@ */ package alfio.model.modification; -import lombok.Data; - import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -@Data + public class UploadBase64FileModification { private byte[] file; @@ -33,6 +31,38 @@ public class UploadBase64FileModification { private String name; private Map<String, String> attributes; + public void setFile(byte[] file) { + this.file = file; + } + + public void setFileAsString(String fileAsString) { + this.fileAsString = fileAsString; + } + + public String getFileAsString() { + return fileAsString; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setAttributes(Map<String, String> attributes) { + this.attributes = attributes; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + public InputStream getInputStream() { return new ByteArrayInputStream(file); } diff --git a/src/main/java/alfio/model/modification/support/LocationDescriptor.java b/src/main/java/alfio/model/modification/support/LocationDescriptor.java index ed9d0b65ab..a00007d16b 100644 --- a/src/main/java/alfio/model/modification/support/LocationDescriptor.java +++ b/src/main/java/alfio/model/modification/support/LocationDescriptor.java @@ -21,18 +21,15 @@ import alfio.model.system.ConfigurationKeys; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.EqualsAndHashCode; -import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.text.StringSubstitutor; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.TimeZone; -@Getter -@EqualsAndHashCode public class LocationDescriptor { @@ -91,11 +88,13 @@ public static ConfigurationKeys.GeoInfoProvider getProvider(Map<ConfigurationKey } private static String mapUrl(ConfigurationKeys.GeoInfoProvider provider) { - switch (provider) { - case GOOGLE: return "https://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&key=${key}&zoom=16&size=400x400&markers=color:blue%7Clabel:E%7C${latitude},${longitude}"; - case HERE: return "https://image.maps.ls.hereapi.com/mia/1.6/mapview?c=${latitude},${longitude}&z=16&w=400&h=400&poi=${latitude},${longitude}&apikey=${key}"; - default: return ""; - } + return switch (provider) { + case GOOGLE -> + "https://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&key=${key}&zoom=16&size=400x400&markers=color:blue%7Clabel:E%7C${latitude},${longitude}"; + case HERE -> + "https://image.maps.ls.hereapi.com/mia/1.6/mapview?c=${latitude},${longitude}&z=16&w=400&h=400&poi=${latitude},${longitude}&apikey=${key}"; + default -> ""; + }; } private static void fillParams(ConfigurationKeys.GeoInfoProvider provider, Map<ConfigurationKeys, ConfigurationManager.MaybeConfiguration> geoConf, Map<String, String> params) { @@ -106,4 +105,31 @@ private static void fillParams(ConfigurationKeys.GeoInfoProvider provider, Map<C } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LocationDescriptor that)) return false; + return Objects.equals(timeZone, that.timeZone) && Objects.equals(latitude, that.latitude) && Objects.equals(longitude, that.longitude) && Objects.equals(mapUrl, that.mapUrl); + } + + @Override + public int hashCode() { + return Objects.hash(timeZone, latitude, longitude, mapUrl); + } + + public String getTimeZone() { + return timeZone; + } + + public String getLatitude() { + return latitude; + } + + public String getLongitude() { + return longitude; + } + + public String getMapUrl() { + return mapUrl; + } } diff --git a/src/main/java/alfio/model/poll/Poll.java b/src/main/java/alfio/model/poll/Poll.java index 0e4944905a..b545e950d3 100644 --- a/src/main/java/alfio/model/poll/Poll.java +++ b/src/main/java/alfio/model/poll/Poll.java @@ -19,15 +19,19 @@ import alfio.model.support.Array; import alfio.model.support.JSONData; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -import lombok.ToString; +import org.apache.commons.lang3.builder.ToStringBuilder; import java.util.List; import java.util.Map; -@Getter -@ToString -public class Poll { +public record Poll(@Column("id") long id, + @Column("status") PollStatus status, + @Column("title") @JSONData Map<String, String> title, + @Column("description") @JSONData Map<String, String> description, + @Column("allowed_tags") @Array List<String> allowedTags, + @Column("poll_order") int order, + @Column("event_id_fk") int eventId, + @Column("organization_id_fk") int organizationId) { public enum PollStatus { @@ -36,33 +40,17 @@ public enum PollStatus { CLOSED } - private final long id; - private final PollStatus status; - private final Map<String, String> title; - private final Map<String, String> description; - private final List<String> allowedTags; - private final int order; - private final int eventId; - private final int organizationId; - - public Poll(@Column("id") long id, - @Column("status") PollStatus status, - @Column("title") @JSONData Map<String, String> title, - @Column("description") @JSONData Map<String, String> description, - @Column("allowed_tags") @Array List<String> allowedTags, - @Column("poll_order") int order, - @Column("event_id_fk") int eventId, - @Column("organization_id_fk") int organizationId) { - - this.id = id; - this.status = status; - this.title = title; - this.description = description; - this.allowedTags = allowedTags; - this.order = order; - this.eventId = eventId; - this.organizationId = organizationId; + @Override + public String toString() { + return new ToStringBuilder(this) + .append("id", id) + .append("status", status) + .append("title", title) + .append("description", description) + .append("allowedTags", allowedTags) + .append("order", order) + .append("eventId", eventId) + .append("organizationId", organizationId) + .toString(); } - - } diff --git a/src/main/java/alfio/model/poll/PollOption.java b/src/main/java/alfio/model/poll/PollOption.java index 092989b0b1..575bdf6301 100644 --- a/src/main/java/alfio/model/poll/PollOption.java +++ b/src/main/java/alfio/model/poll/PollOption.java @@ -18,24 +18,12 @@ import alfio.model.support.JSONData; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; import java.util.Map; -@Getter -public class PollOption { - private final Long id; - private final Long pollId; - private final Map<String, String> title; - private final Map<String, String> description; - public PollOption(@Column("id") Long id, - @Column("poll_id_fk") Long pollId, - @Column("title") @JSONData Map<String, String> title, - @Column("description") @JSONData Map<String, String> description) { - this.id = id; - this.pollId = pollId; - this.title = title; - this.description = description; - } +public record PollOption(@Column("id") Long id, + @Column("poll_id_fk") Long pollId, + @Column("title") @JSONData Map<String, String> title, + @Column("description") @JSONData Map<String, String> description) { } diff --git a/src/main/java/alfio/model/poll/PollOptionStatistics.java b/src/main/java/alfio/model/poll/PollOptionStatistics.java index b8634336d4..60ab5b18a7 100644 --- a/src/main/java/alfio/model/poll/PollOptionStatistics.java +++ b/src/main/java/alfio/model/poll/PollOptionStatistics.java @@ -17,17 +17,7 @@ package alfio.model.poll; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class PollOptionStatistics { - - private final int votes; - private final long optionId; - - public PollOptionStatistics(@Column("votes") int votes, - @Column("poll_option_id_fk") long optionId) { - this.votes = votes; - this.optionId = optionId; - } +public record PollOptionStatistics(@Column("votes") int votes, + @Column("poll_option_id_fk") long optionId) { } diff --git a/src/main/java/alfio/model/poll/PollParticipant.java b/src/main/java/alfio/model/poll/PollParticipant.java index 6fc8b8cb09..ad132c8c75 100644 --- a/src/main/java/alfio/model/poll/PollParticipant.java +++ b/src/main/java/alfio/model/poll/PollParticipant.java @@ -17,27 +17,11 @@ package alfio.model.poll; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class PollParticipant { +public record PollParticipant(@Column("t_id") int id, + @Column("t_first_name") String firstName, + @Column("t_last_name") String lastName, + @Column("t_email_address") String emailAddress, + @Column("tc_name") String categoryName) { - private final int id; - private final String firstName; - private final String lastName; - private final String emailAddress; - private final String categoryName; - - - public PollParticipant(@Column("t_id") int id, - @Column("t_first_name") String firstName, - @Column("t_last_name") String lastName, - @Column("t_email_address") String emailAddress, - @Column("tc_name") String categoryName) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - this.emailAddress = emailAddress; - this.categoryName = categoryName; - } } diff --git a/src/main/java/alfio/model/poll/PollStatistics.java b/src/main/java/alfio/model/poll/PollStatistics.java index f367092aa7..efdee35bba 100644 --- a/src/main/java/alfio/model/poll/PollStatistics.java +++ b/src/main/java/alfio/model/poll/PollStatistics.java @@ -17,19 +17,23 @@ package alfio.model.poll; import alfio.util.MonetaryUtil; -import lombok.AllArgsConstructor; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; import java.util.stream.Collectors; -@AllArgsConstructor public class PollStatistics { private final int totalVotes; private final int allowedParticipants; private final List<PollOptionStatistics> countByOption; + public PollStatistics(int totalVotes, int allowedParticipants, List<PollOptionStatistics> countByOption) { + this.totalVotes = totalVotes; + this.allowedParticipants = allowedParticipants; + this.countByOption = countByOption; + } + public int getTotalVotes() { return totalVotes; } @@ -46,8 +50,8 @@ public List<StatisticDetail> getOptionStatistics() { BigDecimal totalVotes = new BigDecimal(this.totalVotes); return countByOption.stream() .map(o -> { - var percentage = getVotesPercentage(totalVotes, o.getVotes()); - return new StatisticDetail(o.getVotes(), o.getOptionId(), percentage); + var percentage = getVotesPercentage(totalVotes, o.votes()); + return new StatisticDetail(o.votes(), o.optionId(), percentage); }) .collect(Collectors.toList()); } diff --git a/src/main/java/alfio/model/poll/PollWithOptions.java b/src/main/java/alfio/model/poll/PollWithOptions.java index cc23b23185..d220df4919 100644 --- a/src/main/java/alfio/model/poll/PollWithOptions.java +++ b/src/main/java/alfio/model/poll/PollWithOptions.java @@ -18,20 +18,5 @@ import java.util.List; -public class PollWithOptions { - private final Poll poll; - private final List<PollOption> options; - - public PollWithOptions(Poll poll, List<PollOption> options) { - this.poll = poll; - this.options = options; - } - - public List<PollOption> getOptions() { - return options; - } - - public Poll getPoll() { - return poll; - } +public record PollWithOptions(Poll poll, List<PollOption> options) { } diff --git a/src/main/java/alfio/model/result/ErrorCode.java b/src/main/java/alfio/model/result/ErrorCode.java index 574e663719..ce36ca414c 100644 --- a/src/main/java/alfio/model/result/ErrorCode.java +++ b/src/main/java/alfio/model/result/ErrorCode.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Getter; -import lombok.RequiredArgsConstructor; import java.util.function.Supplier; @@ -35,7 +34,7 @@ default String getLocation() { String getDescription(); Object[] getArguments(); - @RequiredArgsConstructor + @Getter enum CategoryError implements ErrorCode { NOT_FOUND("not_found", "Category not found"), @@ -47,6 +46,11 @@ enum CategoryError implements ErrorCode { private final String code; private final String description; + CategoryError(String code, String description) { + this.code = code; + this.description = description; + } + @Override public String toString() { @@ -59,7 +63,7 @@ public Object[] getArguments() { } } - @RequiredArgsConstructor + @Getter enum EventError implements ErrorCode { NOT_FOUND("not_found", "No event has been found"), @@ -68,6 +72,11 @@ enum EventError implements ErrorCode { private final String code; private final String description; + EventError(String code, String description) { + this.code = code; + this.description = description; + } + @Override public String toString() { @@ -80,7 +89,6 @@ public Object[] getArguments() { } } - @RequiredArgsConstructor @Getter enum ReservationError implements ErrorCode { NOT_FOUND("not_found", "No reservation has been found"), @@ -90,6 +98,11 @@ enum ReservationError implements ErrorCode { private final String code; private final String description; + ReservationError(String code, String description) { + this.code = code; + this.description = description; + } + @Override public String toString() { diff --git a/src/main/java/alfio/model/result/Result.java b/src/main/java/alfio/model/result/Result.java index 92def651ab..83b8352802 100644 --- a/src/main/java/alfio/model/result/Result.java +++ b/src/main/java/alfio/model/result/Result.java @@ -17,7 +17,6 @@ package alfio.model.result; import alfio.model.result.ValidationResult.ErrorDescriptor; -import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.tuple.Pair; @@ -29,7 +28,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -@AllArgsConstructor + @Getter public class Result<T> { @@ -37,6 +36,12 @@ public class Result<T> { private final T data; private final List<ErrorCode> errors; + public Result(ResultStatus status, T data, List<ErrorCode> errors) { + this.status = status; + this.data = data; + this.errors = errors; + } + public static <T> Result<T> success(T data) { return new Result<>(ResultStatus.OK, data, Collections.emptyList()); } @@ -84,6 +89,14 @@ public ErrorCode getFirstErrorOrNull() { return errors.get(0); } + public String getFormattedErrors() { + if(isSuccess()) { + return null; + } + return getErrors().stream().map(ErrorCode::getDescription) + .collect(Collectors.joining("\n")); + } + public enum ResultStatus { OK, VALIDATION_ERROR, ERROR } diff --git a/src/main/java/alfio/model/result/ValidationResult.java b/src/main/java/alfio/model/result/ValidationResult.java index a757fd432a..eed5cf115b 100644 --- a/src/main/java/alfio/model/result/ValidationResult.java +++ b/src/main/java/alfio/model/result/ValidationResult.java @@ -17,7 +17,7 @@ package alfio.model.result; import lombok.Getter; -import lombok.ToString; +import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; @@ -88,7 +88,6 @@ public boolean isSuccess() { return errorCount == 0; } - @ToString @Getter public static final class ErrorDescriptor implements ErrorCode { private final String fieldName; @@ -138,6 +137,16 @@ public static ErrorDescriptor fromFieldError(FieldError fieldError) { public static ErrorDescriptor fromObjectError(ObjectError objectError) { return new ErrorDescriptor("", objectError.getObjectName(), objectError.getCode(), objectError.getArguments()); } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("fieldName", fieldName) + .append("message", message) + .append("code", code) + .append("arguments", arguments) + .toString(); + } } @FunctionalInterface diff --git a/src/main/java/alfio/model/subscription/Subscription.java b/src/main/java/alfio/model/subscription/Subscription.java index f31bb44621..5e3cd3eb26 100644 --- a/src/main/java/alfio/model/subscription/Subscription.java +++ b/src/main/java/alfio/model/subscription/Subscription.java @@ -91,11 +91,11 @@ public Subscription(@Column("id") UUID id, this.currency = currency; this.status = status; this.maxEntries = maxEntries; - var zoneId = ZoneId.of(timeZone); - this.zoneId = zoneId; - this.validityFrom = atZone(validityFrom, zoneId); - this.validityTo = atZone(validityTo, zoneId); - this.confirmationTimestamp = atZone(confirmationTimestamp, zoneId); + var zone = ZoneId.of(timeZone); + this.zoneId = zone; + this.validityFrom = atZone(validityFrom, zone); + this.validityTo = atZone(validityTo, zone); + this.confirmationTimestamp = atZone(confirmationTimestamp, zone); } public boolean isValid(Optional<BindingResult> bindingResult) { diff --git a/src/main/java/alfio/model/subscription/SubscriptionPriceContainer.java b/src/main/java/alfio/model/subscription/SubscriptionPriceContainer.java index c693056806..fa8752b001 100644 --- a/src/main/java/alfio/model/subscription/SubscriptionPriceContainer.java +++ b/src/main/java/alfio/model/subscription/SubscriptionPriceContainer.java @@ -18,7 +18,6 @@ import alfio.model.PriceContainer; import alfio.model.PromoCodeDiscount; -import lombok.AllArgsConstructor; import java.math.BigDecimal; import java.util.Optional; @@ -26,13 +25,18 @@ import static alfio.util.MonetaryUtil.centsToUnit; import static alfio.util.MonetaryUtil.unitToCents; -@AllArgsConstructor public class SubscriptionPriceContainer implements PriceContainer { private final Subscription subscription; private final PromoCodeDiscount promoCodeDiscount; private final SubscriptionDescriptor descriptor; + public SubscriptionPriceContainer(Subscription subscription, PromoCodeDiscount promoCodeDiscount, SubscriptionDescriptor descriptor) { + this.subscription = subscription; + this.promoCodeDiscount = promoCodeDiscount; + this.descriptor = descriptor; + } + @Override public int getSrcPriceCts() { diff --git a/src/main/java/alfio/model/subscription/SubscriptionWithUsageDetails.java b/src/main/java/alfio/model/subscription/SubscriptionWithUsageDetails.java index 383dfc1185..6a9b5aa19a 100644 --- a/src/main/java/alfio/model/subscription/SubscriptionWithUsageDetails.java +++ b/src/main/java/alfio/model/subscription/SubscriptionWithUsageDetails.java @@ -18,14 +18,21 @@ import alfio.model.TicketReservationWithEventIdentifier; import lombok.Getter; -import lombok.RequiredArgsConstructor; import java.util.List; -@RequiredArgsConstructor + @Getter public class SubscriptionWithUsageDetails { private final Subscription subscription; private final UsageDetails usageDetails; private final List<TicketReservationWithEventIdentifier> reservations; + + public SubscriptionWithUsageDetails(Subscription subscription, + UsageDetails usageDetails, + List<TicketReservationWithEventIdentifier> reservations) { + this.subscription = subscription; + this.usageDetails = usageDetails; + this.reservations = reservations; + } } diff --git a/src/main/java/alfio/model/subscription/UsageDetails.java b/src/main/java/alfio/model/subscription/UsageDetails.java index 8a9e6f8280..1329bf3682 100644 --- a/src/main/java/alfio/model/subscription/UsageDetails.java +++ b/src/main/java/alfio/model/subscription/UsageDetails.java @@ -16,16 +16,20 @@ */ package alfio.model.subscription; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public class UsageDetails { private final Integer total; private final Integer used; private final Integer available; + public UsageDetails(Integer total, Integer used, Integer available) { + this.total = total; + this.used = used; + this.available = available; + } + public static UsageDetails fromSubscription(Subscription subscription, int usageCount) { int maxEntries = Math.max(subscription.getMaxEntries(), 0); return new UsageDetails(maxEntries == 0 ? null : maxEntries, diff --git a/src/main/java/alfio/model/support/EventBasicInfo.java b/src/main/java/alfio/model/support/EventBasicInfo.java new file mode 100644 index 0000000000..0d1947dc0b --- /dev/null +++ b/src/main/java/alfio/model/support/EventBasicInfo.java @@ -0,0 +1,44 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.support; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class EventBasicInfo { + private final String shortName; + private final String displayName; + + @JsonCreator + public EventBasicInfo(@JsonProperty("shortName") String shortName, + @JsonProperty("displayName") String displayName) { + this.shortName = shortName; + this.displayName = displayName; + } + + public String getShortName() { + return shortName; + } + + public String getDisplayName() { + return displayName; + } + + public String getPublicIdentifier() { + return getShortName(); + } +} diff --git a/src/main/java/alfio/model/support/ReservationInfo.java b/src/main/java/alfio/model/support/ReservationInfo.java new file mode 100644 index 0000000000..ed6e2671a6 --- /dev/null +++ b/src/main/java/alfio/model/support/ReservationInfo.java @@ -0,0 +1,142 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.support; + +import alfio.model.PriceContainer; +import alfio.model.transaction.PaymentProxy; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class ReservationInfo { + private final String id; + private final String invoiceNumber; + private final String firstName; + private final String lastName; + private final String companyName; + private final String taxId; + private final String email; + private final PaymentProxy paymentType; + private final Integer finalPriceCts; + private final Integer srcPriceCts; + private final Integer taxCts; + private final PriceContainer.VatStatus taxStatus; + private final String taxCode; + private final String currency; + private final String confirmationTimestamp; + private final List<TicketInfo> tickets; + + @JsonCreator + ReservationInfo(@JsonProperty("id") String id, + @JsonProperty("invoiceNumber") String invoiceNumber, + @JsonProperty("firstName") String firstName, + @JsonProperty("lastName") String lastName, + @JsonProperty("companyName") String companyName, + @JsonProperty("taxId") String taxId, + @JsonProperty("email") String email, + @JsonProperty("paymentType") PaymentProxy paymentType, + @JsonProperty("finalPriceCts") Integer finalPriceCts, + @JsonProperty("srcPriceCts") Integer srcPriceCts, + @JsonProperty("taxCts") Integer taxCts, + @JsonProperty("taxStatus") PriceContainer.VatStatus taxStatus, + @JsonProperty("taxCode") String taxCode, + @JsonProperty("currency") String currency, + @JsonProperty("confirmationTimestamp") String confirmationTimestamp, + @JsonProperty("tickets") List<TicketInfo> tickets) { + this.id = id; + this.invoiceNumber = invoiceNumber; + this.firstName = firstName; + this.lastName = lastName; + this.companyName = companyName; + this.taxId = taxId; + this.email = email; + this.paymentType = paymentType; + this.finalPriceCts = finalPriceCts; + this.srcPriceCts = srcPriceCts; + this.taxCts = taxCts; + this.taxStatus = taxStatus; + this.taxCode = taxCode; + this.currency = currency; + this.confirmationTimestamp = confirmationTimestamp; + this.tickets = tickets; + } + + public String getId() { + return id; + } + + public String getInvoiceNumber() { + return invoiceNumber; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getEmail() { + return email; + } + + public PaymentProxy getPaymentType() { + return paymentType; + } + + public String getCurrency() { + return currency; + } + + public String getConfirmationTimestamp() { + return confirmationTimestamp; + } + + public List<TicketInfo> getTickets() { + return tickets; + } + + public Integer getFinalPriceCts() { + return finalPriceCts; + } + + public Integer getSrcPriceCts() { + return srcPriceCts; + } + + public PriceContainer.VatStatus getTaxStatus() { + return taxStatus; + } + + public String getTaxCode() { + return taxCode; + } + + public String getCompanyName() { + return companyName; + } + + public String getTaxId() { + return taxId; + } + + public Integer getTaxCts() { + return taxCts; + } +} diff --git a/src/main/java/alfio/model/support/TicketInfo.java b/src/main/java/alfio/model/support/TicketInfo.java new file mode 100644 index 0000000000..38f67139d2 --- /dev/null +++ b/src/main/java/alfio/model/support/TicketInfo.java @@ -0,0 +1,90 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.support; + +import alfio.model.PriceContainer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TicketInfo { + private final String id; + private final String firstName; + private final String lastName; + private final String type; + private final String status; + private final Integer finalPriceCts; + private final Integer srcPriceCts; + private final Integer taxCts; + private final PriceContainer.VatStatus taxStatus; + + @JsonCreator + private TicketInfo(@JsonProperty("id") String id, + @JsonProperty("firstName") String firstName, + @JsonProperty("lastName") String lastName, + @JsonProperty("type") String type, + @JsonProperty("status") String status, + @JsonProperty("finalPriceCts") Integer finalPriceCts, + @JsonProperty("srcPriceCts") Integer srcPriceCts, + @JsonProperty("taxCts") Integer taxCts, + @JsonProperty("taxStatus") PriceContainer.VatStatus taxStatus) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.type = type; + this.status = status; + this.finalPriceCts = finalPriceCts; + this.srcPriceCts = srcPriceCts; + this.taxCts = taxCts; + this.taxStatus = taxStatus; + } + + public String getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getType() { + return type; + } + + public String getStatus() { + return status; + } + + public Integer getFinalPriceCts() { + return finalPriceCts; + } + + public Integer getSrcPriceCts() { + return srcPriceCts; + } + + public Integer getTaxCts() { + return taxCts; + } + + public PriceContainer.VatStatus getTaxStatus() { + return taxStatus; + } +} diff --git a/src/main/java/alfio/model/support/TicketWithAdditionalFields.java b/src/main/java/alfio/model/support/TicketWithAdditionalFields.java index 450c2279be..1586404f17 100644 --- a/src/main/java/alfio/model/support/TicketWithAdditionalFields.java +++ b/src/main/java/alfio/model/support/TicketWithAdditionalFields.java @@ -20,7 +20,6 @@ import alfio.model.Ticket; import alfio.model.TicketFieldConfigurationDescriptionAndValue; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; import lombok.experimental.Delegate; import org.apache.commons.lang3.tuple.Pair; @@ -28,7 +27,6 @@ import java.util.Map; import java.util.stream.Collectors; -@AllArgsConstructor public class TicketWithAdditionalFields { @Delegate(excludes = EventAndOrganizationId.class) @JsonIgnore @@ -36,6 +34,12 @@ public class TicketWithAdditionalFields { @JsonIgnore private final List<TicketFieldConfigurationDescriptionAndValue> additionalFieldsDescriptionsAndValues; + public TicketWithAdditionalFields(Ticket ticket, + List<TicketFieldConfigurationDescriptionAndValue> additionalFieldsDescriptionsAndValues) { + this.ticket = ticket; + this.additionalFieldsDescriptionsAndValues = additionalFieldsDescriptionsAndValues; + } + public Map<String, String> getAdditionalFields() { return additionalFieldsDescriptionsAndValues.stream() .map(af -> Pair.of(af.getLabelDescription(), af.getValueDescription())) diff --git a/src/main/java/alfio/model/system/Configuration.java b/src/main/java/alfio/model/system/Configuration.java index 5414b8624c..887c667187 100644 --- a/src/main/java/alfio/model/system/Configuration.java +++ b/src/main/java/alfio/model/system/Configuration.java @@ -18,12 +18,14 @@ import alfio.model.EventAndOrganizationId; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.EqualsAndHashCode; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import java.util.Objects; import java.util.function.Function; @Getter @@ -39,10 +41,11 @@ public class Configuration implements Comparable<Configuration> { private final boolean internal; - public Configuration(@Column("id") int id, - @Column("c_key") String key, - @Column("c_value") String value, - @Column("configuration_path_level") ConfigurationPathLevel configurationPathLevel) { + @JsonCreator + public Configuration(@JsonProperty("id") @Column("id") int id, + @JsonProperty("key") @Column("c_key") String key, + @JsonProperty("value") @Column("c_value") String value, + @JsonProperty("configurationPathLevel") @Column("configuration_path_level") ConfigurationPathLevel configurationPathLevel) { this.id = id; this.key = key; this.value = value; @@ -70,10 +73,9 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (!(obj instanceof Configuration)) { + if (!(obj instanceof Configuration o)) { return false; } - Configuration o = (Configuration) obj; return new EqualsBuilder().append(configurationKey, o.configurationKey).append(configurationPathLevel, configurationPathLevel).isEquals(); } @@ -87,7 +89,6 @@ public interface ConfigurationPath { ConfigurationPathLevel pathLevel(); } - @EqualsAndHashCode public static class SystemConfigurationPath implements ConfigurationPath { @Override public ConfigurationPathLevel pathLevel() { @@ -96,7 +97,7 @@ public ConfigurationPathLevel pathLevel() { } - @EqualsAndHashCode + @Getter public static class OrganizationConfigurationPath implements ConfigurationPath { @@ -111,9 +112,20 @@ public ConfigurationPathLevel pathLevel() { return ConfigurationPathLevel.ORGANIZATION; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrganizationConfigurationPath that = (OrganizationConfigurationPath) o; + return id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } } - @EqualsAndHashCode @Getter public static class EventConfigurationPath implements ConfigurationPath { @@ -131,9 +143,20 @@ public ConfigurationPathLevel pathLevel() { return ConfigurationPathLevel.EVENT; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EventConfigurationPath that = (EventConfigurationPath) o; + return organizationId == that.organizationId && id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash(organizationId, id); + } } - @EqualsAndHashCode @Getter public static class TicketCategoryConfigurationPath implements ConfigurationPath { @@ -152,6 +175,18 @@ public ConfigurationPathLevel pathLevel() { return ConfigurationPathLevel.TICKET_CATEGORY; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TicketCategoryConfigurationPath that = (TicketCategoryConfigurationPath) o; + return organizationId == that.organizationId && eventId == that.eventId && id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash(organizationId, eventId, id); + } } public static ConfigurationPath system() { @@ -169,7 +204,6 @@ private static ConfigurationPath ticketCategory(int organizationId, int eventId, // @Getter - @EqualsAndHashCode public static class ConfigurationPathKey { private final ConfigurationPath path; private final ConfigurationKeys key; @@ -178,6 +212,19 @@ private ConfigurationPathKey(ConfigurationPath path, ConfigurationKeys key) { this.path = path; this.key = key; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConfigurationPathKey that = (ConfigurationPathKey) o; + return Objects.equals(path, that.path) && key == that.key; + } + + @Override + public int hashCode() { + return Objects.hash(path, key); + } } // diff --git a/src/main/java/alfio/model/system/ConfigurationKeys.java b/src/main/java/alfio/model/system/ConfigurationKeys.java index 9327b1c0b3..40a9098593 100644 --- a/src/main/java/alfio/model/system/ConfigurationKeys.java +++ b/src/main/java/alfio/model/system/ConfigurationKeys.java @@ -35,6 +35,7 @@ public enum ConfigurationKeys { NOT_RECOGNIZED("option not recognized", true, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.noneOf(ConfigurationPathLevel.class)), INIT_COMPLETED("init succeeded", true, SettingCategory.GENERAL, ComponentType.BOOLEAN, false, EnumSet.noneOf(ConfigurationPathLevel.class), BooleanUtils.FALSE), + SYSTEM_API_KEY("System API Key", true, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.noneOf(ConfigurationPathLevel.class)), SHOW_PROJECT_BANNER("project banner dismissed", true, SettingCategory.GENERAL, ComponentType.BOOLEAN, false, EnumSet.noneOf(ConfigurationPathLevel.class), BooleanUtils.TRUE), @Deprecated SUPPORTED_LANGUAGES("supported languages", true, SettingCategory.GENERAL, ComponentType.LIST, false, EnumSet.of(SYSTEM)), @@ -42,6 +43,7 @@ public enum ConfigurationKeys { BASE_URL("Base application url", false, SettingCategory.GENERAL, ComponentType.TEXT, true, EnumSet.of(SYSTEM)), GLOBAL_PRIVACY_POLICY("Global Privacy Policy URL (to be displayed on the event list)", false, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), GLOBAL_TERMS("Global Terms And Conditions (to be displayed on the event list)", false, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), + ANNOUNCEMENT_BANNER_CONTENT("Announcement banner content", false, SettingCategory.GENERAL, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM)), MAPS_PROVIDER("Select the maps provider (None, Google, Here)", false, SettingCategory.MAP, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), MAPS_CLIENT_API_KEY("Google maps' client api key", false, SettingCategory.MAP, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), @@ -120,9 +122,12 @@ public enum ConfigurationKeys { MAX_EMAIL_PER_CYCLE("How many e-mail should be managed within 5 sec.", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), MAIL_REPLY_TO("Reply-to address", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), + MAIL_SET_ORG_REPLY_TO("Set organizer email as reply-to (default: false)", false, SettingCategory.MAIL, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM), "false"), MAIL_SYSTEM_NOTIFICATION_CC("Add additional CC when the system send notifications to the event organizer, can insert multiple email (comma separated)", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), + MAIL_FOOTER("Email footer", false, SettingCategory.MAIL, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), + //smtp configuration related keys SMTP_HOST("SMTP hostname", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), SMTP_PORT("SMTP port", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), @@ -134,6 +139,7 @@ public enum ConfigurationKeys { BANK_TRANSFER_ENABLED("Bank transfer enabled", false, SettingCategory.PAYMENT_OFFLINE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION), BooleanUtils.FALSE), DEFERRED_BANK_TRANSFER_ENABLED("Send payment instructions manually (default false)", false, SettingCategory.PAYMENT_OFFLINE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), + SHOW_ONLY_BASIC_INSTRUCTIONS("Display only basic payment instructions (default false)", false, SettingCategory.PAYMENT_OFFLINE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL("Send payment confirmation email (default true)", false, SettingCategory.PAYMENT_OFFLINE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.TRUE), OFFLINE_PAYMENT_DAYS("Maximum number of days allowed to pay an offline ticket", false, SettingCategory.PAYMENT_OFFLINE, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), OFFLINE_REMINDER_HOURS("How many hours before expiration should be sent a reminder e-mail for offline payments?", false, SettingCategory.PAYMENT_OFFLINE, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), @@ -212,7 +218,8 @@ public enum ConfigurationKeys { VAT_NUMBER_IS_REQUIRED("VAT/GST Number is required for Business Customers (default: false)", false, SettingCategory.INVOICE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), GENERATE_ONLY_INVOICE("Always generate an invoice for paid reservations (default: false)", false, SettingCategory.INVOICE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), REUSE_INVOICE_NUMBER_FOR_CREDIT_NOTE("Reuse invoice number as Credit Note number (default: true)", false, SettingCategory.INVOICE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION), BooleanUtils.TRUE), - ENABLE_ITALY_E_INVOICING("Enable the support for italian e-invoicing", false, SettingCategory.INVOICE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), + ENABLE_ITALY_E_INVOICING("Enable the support for italian e-invoicing (default: false)", false, SettingCategory.INVOICE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), + ITALY_E_INVOICING_SEND_PROFORMA("Italian e-invoicing: send proforma invoice (default: false)", false, SettingCategory.INVOICE, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.FALSE), ENABLE_EU_VAT_DIRECTIVE("Enable VAT Reverse Charge (default: false)", false, SettingCategory.INVOICE_EU, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION), BooleanUtils.FALSE), ENABLE_REVERSE_CHARGE_ONLINE("Enable VAT Reverse Charge for online tickets (if set overrides global reverse charge, default: true)", false, SettingCategory.INVOICE_EU, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION), BooleanUtils.TRUE), ENABLE_REVERSE_CHARGE_IN_PERSON("Enable VAT Reverse Charge for in-person tickets (if set overrides global reverse charge, default: true)", false, SettingCategory.INVOICE_EU, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION), BooleanUtils.TRUE), @@ -222,17 +229,23 @@ public enum ConfigurationKeys { EU_COUNTRIES_LIST("EU Countries", true, SettingCategory.INVOICE_EU, ComponentType.LIST, false, EnumSet.of(SYSTEM)), @Deprecated EU_VAT_API_ADDRESS("EU VAT API address", true, SettingCategory.INVOICE_EU, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), + APPLY_TAX_TO_CATEGORY("Apply taxes to this Category (default: true)", false, SettingCategory.GENERAL, ComponentType.BOOLEAN, false, EnumSet.of(TICKET_CATEGORY), BooleanUtils.TRUE), // //PASSBOOK - ENABLE_PASS("Enable Apple(tm) Wallet integration", false, SettingCategory.PASS_INTEGRATION, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM), BooleanUtils.FALSE), + ENABLE_PASS("Enable Apple(tm) Wallet integration (default: false)", false, SettingCategory.PASS_INTEGRATION, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM), BooleanUtils.FALSE), PASSBOOK_TYPE_IDENTIFIER("Passbook type identifier", false, SettingCategory.PASS_INTEGRATION, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), PASSBOOK_TEAM_IDENTIFIER("Passbook team identifier", false, SettingCategory.PASS_INTEGRATION, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), PASSBOOK_KEYSTORE("Passbook keystore(base64 encoded keystore)", false, SettingCategory.PASS_INTEGRATION, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM)), PASSBOOK_KEYSTORE_PASSWORD("Passbook keystore password", false, SettingCategory.PASS_INTEGRATION, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), PASSBOOK_PRIVATE_KEY_ALIAS("Passbook Private Key alias", false, SettingCategory.PASS_INTEGRATION, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), + //WALLET + ENABLE_WALLET("Enable Google Wallet(tm) integration (default: false)", false, SettingCategory.WALLET_INTEGRATION, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM), BooleanUtils.FALSE), + WALLET_ISSUER_IDENTIFIER("Google Wallet Issuer ID", false, SettingCategory.WALLET_INTEGRATION, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), + WALLET_SERVICE_ACCOUNT_KEY("Google Wallet Service Account Key in JSON format", false, SettingCategory.WALLET_INTEGRATION, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM)), + WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS("Overwrite previous EventClass and EventObject definitions (use after code changes affecting the JSON, default: false)", false, SettingCategory.WALLET_INTEGRATION, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM), BooleanUtils.FALSE), //CHECK-IN CHECK_IN_STATS("Display check-in statistics in mobile apps", false, SettingCategory.GENERAL, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT), BooleanUtils.TRUE), @@ -248,7 +261,8 @@ public enum ConfigurationKeys { SECURITY_CSP_REPORT_ENABLED("Enable Content-Security-Policy reporting (default: false)", false, SettingCategory.GENERAL, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM), BooleanUtils.FALSE), SECURITY_CSP_REPORT_URI("Define Content-Security-Policy reporting URI (default: /report-csp-violation)", false, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), - EMBED_ALLOWED_ORIGINS("Allowed origins for embedding the reservation process in an iFrame. Separate different origins with a newline", false, SettingCategory.GENERAL, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), + EMBED_ALLOWED_ORIGINS("Allowed origins. Separate different origins with a newline", false, SettingCategory.RESERVATION_EMBED, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM)), + EMBED_POST_MESSAGE_ORIGIN("URI to notify container (via PostMessage API) for Reservation success / cancel", false, SettingCategory.RESERVATION_EMBED, ComponentType.TEXT, false, EnumSet.of(SYSTEM)), // TRANSLATION_OVERRIDE("Translation override (json)", false, SettingCategory.TRANSLATIONS, ComponentType.TEXTAREA, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), @@ -268,6 +282,7 @@ public enum ConfigurationKeys { public enum SettingCategory { GENERAL("General settings"), RESERVATION_UI("Reservation Process UI"), + RESERVATION_EMBED("Embedding reservation process"), PAYMENT("Payment"), PAYMENT_STRIPE("Stripe.com settings"), PAYMENT_SAFERPAY("Saferpay settings"), @@ -281,6 +296,7 @@ public enum SettingCategory { MAP("Maps settings"), TRANSLATIONS("Translations"), PASS_INTEGRATION("Pass Integration"), + WALLET_INTEGRATION("Google Wallet Integration"), WAITING_LIST("Waiting List"), IMPORT_ATTENDEE("Import Attendees"), OPENID("Public users Authentication"), diff --git a/src/main/java/alfio/model/system/command/FinalizeReservation.java b/src/main/java/alfio/model/system/command/FinalizeReservation.java new file mode 100644 index 0000000000..4fcde06830 --- /dev/null +++ b/src/main/java/alfio/model/system/command/FinalizeReservation.java @@ -0,0 +1,91 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.system.command; + +import alfio.manager.payment.PaymentSpecification; +import alfio.model.TicketReservation.TicketReservationStatus; +import alfio.model.transaction.PaymentProxy; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +public class FinalizeReservation { + private final PaymentSpecification paymentSpecification; + private final PaymentProxy paymentProxy; + private final boolean sendReservationConfirmationEmail; + private final boolean sendTickets; + private final String username; + private final TicketReservationStatus originalStatus; + + @JsonCreator + public FinalizeReservation(@JsonProperty("paymentSpecification") PaymentSpecification paymentSpecification, + @JsonProperty("paymentProxy") PaymentProxy paymentProxy, + @JsonProperty("sendReservationConfirmationEmail") boolean sendReservationConfirmationEmail, + @JsonProperty("sendTickets") boolean sendTickets, + @JsonProperty("username") String username, + @JsonProperty("originalStatus") TicketReservationStatus originalStatus) { + this.paymentSpecification = paymentSpecification; + this.paymentProxy = paymentProxy; + this.sendReservationConfirmationEmail = sendReservationConfirmationEmail; + this.sendTickets = sendTickets; + this.username = username; + this.originalStatus = originalStatus; + } + + public PaymentSpecification getPaymentSpecification() { + return paymentSpecification; + } + + public PaymentProxy getPaymentProxy() { + return paymentProxy; + } + + public boolean isSendReservationConfirmationEmail() { + return sendReservationConfirmationEmail; + } + + public boolean isSendTickets() { + return sendTickets; + } + + public String getUsername() { + return username; + } + + public TicketReservationStatus getOriginalStatus() { + return originalStatus; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FinalizeReservation)) { + return false; + } + FinalizeReservation that = (FinalizeReservation) o; + return sendReservationConfirmationEmail == that.sendReservationConfirmationEmail + && sendTickets == that.sendTickets + && paymentSpecification.equals(that.paymentSpecification) + && paymentProxy == that.paymentProxy + && Objects.equals(username, that.username); + } + + @Override + public int hashCode() { + return Objects.hash(paymentSpecification, paymentProxy, sendReservationConfirmationEmail, sendTickets, username); + } +} diff --git a/src/main/java/alfio/model/system/command/InvalidateAccess.java b/src/main/java/alfio/model/system/command/InvalidateAccess.java new file mode 100644 index 0000000000..bbc6f0e3ca --- /dev/null +++ b/src/main/java/alfio/model/system/command/InvalidateAccess.java @@ -0,0 +1,48 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.system.command; + +import alfio.model.Event; +import alfio.model.Ticket; +import alfio.model.metadata.TicketMetadataContainer; + +/** + * Signals that access for the ticket must be invalidated on external systems + */ +public class InvalidateAccess { + private final Ticket ticket; + private final TicketMetadataContainer ticketMetadataContainer; + private final Event event; + + public InvalidateAccess(Ticket ticket, TicketMetadataContainer ticketMetadataContainer, Event event) { + this.ticket = ticket; + this.ticketMetadataContainer = ticketMetadataContainer; + this.event = event; + } + + public Ticket getTicket() { + return ticket; + } + + public TicketMetadataContainer getTicketMetadataContainer() { + return ticketMetadataContainer; + } + + public Event getEvent() { + return event; + } +} diff --git a/src/main/java/alfio/model/transaction/Transaction.java b/src/main/java/alfio/model/transaction/Transaction.java index 40db3845cc..1effc739bc 100644 --- a/src/main/java/alfio/model/transaction/Transaction.java +++ b/src/main/java/alfio/model/transaction/Transaction.java @@ -28,6 +28,7 @@ @Getter public class Transaction { + public static final String NOTES_KEY = "transactionNotes"; public enum Status { PENDING, OFFLINE_MATCHING_PAYMENT_FOUND, @@ -94,4 +95,12 @@ public boolean isPotentialMatch() { public String getFormattedAmount() { return MonetaryUtil.formatCents(priceInCents, currency); } + + public String getNotes() { + return metadata == null ? null : metadata.get(NOTES_KEY); + } + + public boolean isTimestampEditable() { + return paymentProxy == PaymentProxy.OFFLINE || paymentProxy == PaymentProxy.ON_SITE; + } } diff --git a/src/main/java/alfio/model/transaction/TransactionRequest.java b/src/main/java/alfio/model/transaction/TransactionRequest.java index 821bcf4b1e..b9450114a0 100644 --- a/src/main/java/alfio/model/transaction/TransactionRequest.java +++ b/src/main/java/alfio/model/transaction/TransactionRequest.java @@ -18,16 +18,11 @@ import alfio.model.BillingDetails; import alfio.model.TotalPrice; -import lombok.Data; -@Data -public class TransactionRequest { +public record TransactionRequest(TotalPrice price, BillingDetails billingDetails) { private static final TransactionRequest EMPTY = new TransactionRequest(null, null); - private final TotalPrice price; - private final BillingDetails billingDetails; - public static TransactionRequest empty() { return EMPTY; } diff --git a/src/main/java/alfio/model/transaction/token/MollieToken.java b/src/main/java/alfio/model/transaction/token/MollieToken.java index 94fd3ac62c..a4384aacbc 100644 --- a/src/main/java/alfio/model/transaction/token/MollieToken.java +++ b/src/main/java/alfio/model/transaction/token/MollieToken.java @@ -19,14 +19,17 @@ import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.PaymentToken; -import lombok.AllArgsConstructor; -@AllArgsConstructor public class MollieToken implements PaymentToken { private final String paymentId; private final PaymentMethod paymentMethod; + public MollieToken(String paymentId, PaymentMethod paymentMethod) { + this.paymentId = paymentId; + this.paymentMethod = paymentMethod; + } + @Override public String getToken() { return paymentId; diff --git a/src/main/java/alfio/model/transaction/token/PayPalToken.java b/src/main/java/alfio/model/transaction/token/PayPalToken.java index 259ea25664..3a8d5fba8a 100644 --- a/src/main/java/alfio/model/transaction/token/PayPalToken.java +++ b/src/main/java/alfio/model/transaction/token/PayPalToken.java @@ -20,9 +20,7 @@ import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.PaymentToken; import lombok.Getter; -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor @Getter public class PayPalToken implements PaymentToken { @@ -30,6 +28,12 @@ public class PayPalToken implements PaymentToken { private final String paymentId; private final String hmac; + public PayPalToken(String payerId, String paymentId, String hmac) { + this.payerId = payerId; + this.paymentId = paymentId; + this.hmac = hmac; + } + @Override public String getToken() { return paymentId; diff --git a/src/main/java/alfio/model/transaction/token/StripeCreditCardToken.java b/src/main/java/alfio/model/transaction/token/StripeCreditCardToken.java index ce6d0c2931..34db799521 100644 --- a/src/main/java/alfio/model/transaction/token/StripeCreditCardToken.java +++ b/src/main/java/alfio/model/transaction/token/StripeCreditCardToken.java @@ -19,13 +19,15 @@ import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.PaymentToken; -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor public class StripeCreditCardToken implements PaymentToken { private final String token; + public StripeCreditCardToken(String token) { + this.token = token; + } + @Override public String getToken() { return token; diff --git a/src/main/java/alfio/model/transaction/token/StripeSCACreditCardToken.java b/src/main/java/alfio/model/transaction/token/StripeSCACreditCardToken.java index 20cf80dcc1..1b5160ae10 100644 --- a/src/main/java/alfio/model/transaction/token/StripeSCACreditCardToken.java +++ b/src/main/java/alfio/model/transaction/token/StripeSCACreditCardToken.java @@ -20,9 +20,7 @@ import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.TransactionInitializationToken; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; -@AllArgsConstructor public class StripeSCACreditCardToken implements TransactionInitializationToken { @JsonIgnore @@ -31,6 +29,12 @@ public class StripeSCACreditCardToken implements TransactionInitializationToken private final String chargeId; private final String clientSecret; + public StripeSCACreditCardToken(String paymentIntentId, String chargeId, String clientSecret) { + this.paymentIntentId = paymentIntentId; + this.chargeId = chargeId; + this.clientSecret = clientSecret; + } + @Override public String getToken() { return chargeId; diff --git a/src/main/java/alfio/model/transaction/webhook/EmptyWebhookPayload.java b/src/main/java/alfio/model/transaction/webhook/EmptyWebhookPayload.java index 2aacfe3af8..6bb87de796 100644 --- a/src/main/java/alfio/model/transaction/webhook/EmptyWebhookPayload.java +++ b/src/main/java/alfio/model/transaction/webhook/EmptyWebhookPayload.java @@ -17,18 +17,21 @@ package alfio.model.transaction.webhook; import alfio.model.transaction.TransactionWebhookPayload; -import lombok.AllArgsConstructor; /** * Special {@link alfio.model.transaction.TransactionWebhookPayload} for providers that don't send a payload * along with the Webhook */ -@AllArgsConstructor public class EmptyWebhookPayload implements TransactionWebhookPayload { private final String reservationId; private final Status status; + public EmptyWebhookPayload(String reservationId, Status status) { + this.reservationId = reservationId; + this.status = status; + } + @Override public String getPayload() { return "empty"; diff --git a/src/main/java/alfio/model/transaction/webhook/MollieWebhookPayload.java b/src/main/java/alfio/model/transaction/webhook/MollieWebhookPayload.java index 89d53bdc6b..676075f4d1 100644 --- a/src/main/java/alfio/model/transaction/webhook/MollieWebhookPayload.java +++ b/src/main/java/alfio/model/transaction/webhook/MollieWebhookPayload.java @@ -18,9 +18,7 @@ import alfio.model.PurchaseContext; import alfio.model.transaction.TransactionWebhookPayload; -import lombok.AllArgsConstructor; -@AllArgsConstructor public class MollieWebhookPayload implements TransactionWebhookPayload { private final String paymentId; @@ -28,6 +26,13 @@ public class MollieWebhookPayload implements TransactionWebhookPayload { private final String purchaseContextIdentifier; private final String reservationId; + public MollieWebhookPayload(String paymentId, PurchaseContext.PurchaseContextType purchaseContextType, String purchaseContextIdentifier, String reservationId) { + this.paymentId = paymentId; + this.purchaseContextType = purchaseContextType; + this.purchaseContextIdentifier = purchaseContextIdentifier; + this.reservationId = reservationId; + } + @Override public String getPayload() { return getPaymentId(); diff --git a/src/main/java/alfio/model/transaction/webhook/StripeChargeTransactionWebhookPayload.java b/src/main/java/alfio/model/transaction/webhook/StripeChargeTransactionWebhookPayload.java index 653782dcff..0471a45584 100644 --- a/src/main/java/alfio/model/transaction/webhook/StripeChargeTransactionWebhookPayload.java +++ b/src/main/java/alfio/model/transaction/webhook/StripeChargeTransactionWebhookPayload.java @@ -18,14 +18,17 @@ import alfio.model.transaction.TransactionWebhookPayload; import com.stripe.model.Charge; -import lombok.AllArgsConstructor; -@AllArgsConstructor public class StripeChargeTransactionWebhookPayload implements TransactionWebhookPayload { private final String type; private final Charge payload; + public StripeChargeTransactionWebhookPayload(String type, Charge payload) { + this.type = type; + this.payload = payload; + } + @Override public Charge getPayload() { return payload; diff --git a/src/main/java/alfio/model/transaction/webhook/StripePaymentIntentWebhookPayload.java b/src/main/java/alfio/model/transaction/webhook/StripePaymentIntentWebhookPayload.java index 395881c532..dd17185ef6 100644 --- a/src/main/java/alfio/model/transaction/webhook/StripePaymentIntentWebhookPayload.java +++ b/src/main/java/alfio/model/transaction/webhook/StripePaymentIntentWebhookPayload.java @@ -18,14 +18,17 @@ import alfio.model.transaction.TransactionWebhookPayload; import com.stripe.model.PaymentIntent; -import lombok.AllArgsConstructor; -@AllArgsConstructor public class StripePaymentIntentWebhookPayload implements TransactionWebhookPayload { private final String type; private final PaymentIntent payload; + public StripePaymentIntentWebhookPayload(String type, PaymentIntent payload) { + this.type = type; + this.payload = payload; + } + @Override public PaymentIntent getPayload() { return payload; diff --git a/src/main/java/alfio/model/user/Organization.java b/src/main/java/alfio/model/user/Organization.java index 304634d7ad..9edf811c27 100644 --- a/src/main/java/alfio/model/user/Organization.java +++ b/src/main/java/alfio/model/user/Organization.java @@ -57,12 +57,10 @@ public Organization(@Column("id") int id, public boolean equals(Object o) { if (this == o) return true; - if (! (o instanceof Organization)) { + if (! (o instanceof Organization that)) { return false; } - Organization that = (Organization) o; - return new EqualsBuilder() .append(id, that.id) .isEquals(); @@ -79,7 +77,7 @@ public int hashCode() { * @deprecated use {@link #getExternalId()} * @return the external ID, if present */ - @Deprecated + @Deprecated(forRemoval = true) public String getNameOpenId() { return externalId; } diff --git a/src/main/java/alfio/model/user/PublicUserProfile.java b/src/main/java/alfio/model/user/PublicUserProfile.java index 7b3d946622..5261b6ee28 100644 --- a/src/main/java/alfio/model/user/PublicUserProfile.java +++ b/src/main/java/alfio/model/user/PublicUserProfile.java @@ -24,8 +24,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import lombok.Getter; -import java.util.AbstractMap; -import java.util.List; import java.util.Map; @Getter diff --git a/src/main/java/alfio/model/user/Role.java b/src/main/java/alfio/model/user/Role.java index 41e64a0ef7..9e62ecf23d 100644 --- a/src/main/java/alfio/model/user/Role.java +++ b/src/main/java/alfio/model/user/Role.java @@ -19,21 +19,23 @@ import lombok.Getter; import java.util.Arrays; +import java.util.EnumSet; +import java.util.Set; @Getter public enum Role { - ADMIN("ROLE_ADMIN", "Administrator", RoleTarget.ADMIN), - OWNER("ROLE_OWNER", "Organization owner", RoleTarget.USER), - SUPERVISOR("ROLE_SUPERVISOR", "Check-in supervisor", RoleTarget.USER), - OPERATOR("ROLE_OPERATOR", "Check-in operator", RoleTarget.API_KEY), - SPONSOR("ROLE_SPONSOR", "Sponsor", RoleTarget.API_KEY), - API_CONSUMER("ROLE_API_CLIENT", "API Client", RoleTarget.API_KEY); + ADMIN("ROLE_ADMIN", "Administrator", EnumSet.of(RoleTarget.ADMIN)), + OWNER("ROLE_OWNER", "Organization owner", EnumSet.of(RoleTarget.USER)), + SUPERVISOR("ROLE_SUPERVISOR", "Check-in supervisor", EnumSet.of(RoleTarget.USER, RoleTarget.API_KEY)), + OPERATOR("ROLE_OPERATOR", "Check-in operator", EnumSet.of(RoleTarget.API_KEY)), + SPONSOR("ROLE_SPONSOR", "Sponsor", EnumSet.of(RoleTarget.API_KEY)), + API_CONSUMER("ROLE_API_CLIENT", "API Client", EnumSet.of(RoleTarget.API_KEY)); private final String roleName; private final String description; - private final RoleTarget target; + private final Set<RoleTarget> target; - Role(String roleName, String description, RoleTarget target) { + Role(String roleName, String description, Set<RoleTarget> target) { this.roleName = roleName; this.description = description; this.target = target; diff --git a/src/main/java/alfio/model/user/UserWithOrganizations.java b/src/main/java/alfio/model/user/UserWithOrganizations.java index d7b956ee1f..496f6c503d 100644 --- a/src/main/java/alfio/model/user/UserWithOrganizations.java +++ b/src/main/java/alfio/model/user/UserWithOrganizations.java @@ -17,12 +17,10 @@ package alfio.model.user; import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.RequiredArgsConstructor; import lombok.experimental.Delegate; import java.util.List; -@RequiredArgsConstructor public class UserWithOrganizations { @Delegate @JsonIgnore @@ -30,6 +28,12 @@ public class UserWithOrganizations { private final List<Organization> memberOf; private final List<Role> roles; + public UserWithOrganizations(User user, List<Organization> memberOf, List<Role> roles) { + this.user = user; + this.memberOf = memberOf; + this.roles = roles; + } + public List<Organization> getMemberOf() { return memberOf; } diff --git a/src/main/java/alfio/model/user/join/UserOrganization.java b/src/main/java/alfio/model/user/join/UserOrganization.java index 4da0ab41c5..e4f76c9019 100644 --- a/src/main/java/alfio/model/user/join/UserOrganization.java +++ b/src/main/java/alfio/model/user/join/UserOrganization.java @@ -17,16 +17,6 @@ package alfio.model.user.join; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; -@Getter -public class UserOrganization { - - private final int userId; - private final int organizationId; - - public UserOrganization(@Column("user_id") int userId, @Column("org_id") int organizationId) { - this.userId = userId; - this.organizationId = organizationId; - } +public record UserOrganization(@Column("user_id") int userId, @Column("org_id") int organizationId) { } diff --git a/src/main/java/alfio/repository/AdditionalServiceItemRepository.java b/src/main/java/alfio/repository/AdditionalServiceItemRepository.java index e576d1a9f2..fc180821b7 100644 --- a/src/main/java/alfio/repository/AdditionalServiceItemRepository.java +++ b/src/main/java/alfio/repository/AdditionalServiceItemRepository.java @@ -68,7 +68,7 @@ List<BookedAdditionalService> getAdditionalServicesBookedForReservation(@Bind("r "select" + " ai.uuid ai_uuid, ai.creation ai_creation, ai.last_modified ai_last_modified, ai.final_price_cts ai_final_price_cts, ai.currency_code ai_currency_code, ai.vat_cts ai_vat_cts, ai.discount_cts ai_discount_cts," + " tr.id tr_uuid, tr.first_name tr_first_name, tr.last_name tr_last_name, tr.email_address tr_email_address," + - " asv.service_type as_type, asd.value as_title" + + " asv.service_type as_type, asd.value as_title, ai.status as ai_status " + " from additional_service_item ai" + " join additional_service asv on ai.additional_service_id_fk = asv.id" + " join tickets_reservation tr on ai.tickets_reservation_uuid = tr.id" + @@ -76,7 +76,8 @@ List<BookedAdditionalService> getAdditionalServicesBookedForReservation(@Bind("r " where ai.event_id_fk = :eventId" + " and asv.service_type = :additionalServiceType" + " and asd.type = 'TITLE'" + - " and asd.locale = :locale" + " and asd.locale = :locale " + + " and ai.status in ('ACQUIRED', 'CHECKED_IN', 'TO_BE_PAID') " ) List<AdditionalServiceItemExport> getAdditionalServicesOfTypeForEvent(@Bind("eventId") int eventId, @Bind("additionalServiceType") String additionalServiceType, diff --git a/src/main/java/alfio/repository/AdditionalServiceRepository.java b/src/main/java/alfio/repository/AdditionalServiceRepository.java index 453c768630..59a4d301ae 100644 --- a/src/main/java/alfio/repository/AdditionalServiceRepository.java +++ b/src/main/java/alfio/repository/AdditionalServiceRepository.java @@ -17,6 +17,8 @@ package alfio.repository; import alfio.model.AdditionalService; +import alfio.model.AdditionalServiceItem; +import alfio.model.AdditionalServiceItem.AdditionalServiceItemStatus; import ch.digitalfondue.npjt.*; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -32,12 +34,16 @@ public interface AdditionalServiceRepository { NamedParameterJdbcTemplate getJdbcTemplate(); - default Map<Integer, Integer> getCount(int eventId) { - Map<Integer, Integer> res = new HashMap<>(); - getJdbcTemplate().query("select count(*) as cnt, additional_service_id_fk from additional_service_item where event_id_fk = :eventId group by additional_service_id_fk", + default Map<Integer, Map<AdditionalServiceItemStatus, Integer>> getCount(int eventId) { + Map<Integer, Map<AdditionalServiceItemStatus, Integer>> res = new HashMap<>(); + getJdbcTemplate().query("select count(*) as cnt, additional_service_id_fk, status from additional_service_item where event_id_fk = :eventId group by additional_service_id_fk, status", Collections.singletonMap("eventId", eventId), rse -> { - res.put(rse.getInt("additional_service_id_fk"), rse.getInt("cnt")); + var additionalServiceId = rse.getInt("additional_service_id_fk"); + res.putIfAbsent(additionalServiceId, new EnumMap<>(AdditionalServiceItem.AdditionalServiceItemStatus.class)); + var statusCount = res.get(additionalServiceId); + var status = AdditionalServiceItem.AdditionalServiceItemStatus.valueOf(rse.getString("status")); + statusCount.put(status, rse.getInt("cnt")); } ); return res; diff --git a/src/main/java/alfio/repository/AdditionalServiceTextRepository.java b/src/main/java/alfio/repository/AdditionalServiceTextRepository.java index ffbcc0722e..6150f8d750 100644 --- a/src/main/java/alfio/repository/AdditionalServiceTextRepository.java +++ b/src/main/java/alfio/repository/AdditionalServiceTextRepository.java @@ -56,17 +56,17 @@ default Map<Integer, Map<AdditionalServiceText.TextType, Map<String, String>>> g Map<Integer, Map<AdditionalServiceText.TextType, Map<String, String>>> res = new HashMap<>(); findAllByAdditionalServiceIds(additionalServiceIds).forEach(t -> { - var id = t.getAdditionalServiceId(); + var id = t.additionalServiceId(); if (!res.containsKey(id)) { res.put(id, new EnumMap<>(AdditionalServiceText.TextType.class)); } - if(!res.get(id).containsKey(t.getType())) { - res.get(id).put(t.getType(), new HashMap<>()); + if(!res.get(id).containsKey(t.type())) { + res.get(id).put(t.type(), new HashMap<>()); } - res.get(id).get(t.getType()).put(t.getLocale(), t.getValue()); + res.get(id).get(t.type()).put(t.locale(), t.value()); }); return res; diff --git a/src/main/java/alfio/repository/AuditingRepository.java b/src/main/java/alfio/repository/AuditingRepository.java index 9357a78ab2..34614fc49e 100644 --- a/src/main/java/alfio/repository/AuditingRepository.java +++ b/src/main/java/alfio/repository/AuditingRepository.java @@ -74,6 +74,11 @@ default int insert(String reservationId, Integer userId, PurchaseContext p, Audi @Query("select count(*) from auditing_user where reservation_id = :reservationId and event_type = :eventType") Integer countAuditsOfTypeForReservation(@Bind("reservationId") String reservationId, @Bind("eventType") Audit.EventType eventType); + @Query("select count(*) from auditing_user where reservation_id = :reservationId and entity_id = :ticketId::text and event_type = :eventType") + Integer countAuditsOfTypeForTicket(@Bind("reservationId") String reservationId, + @Bind("ticketId") int ticketId, + @Bind("eventType") Audit.EventType eventType); + @Query("select count(*) from auditing_user where reservation_id = :reservationId and event_type in (:eventTypes) and date_trunc('day', :referenceDate::timestamp) = date_trunc('day', event_time)") Integer countAuditsOfTypesInTheSameDay(@Bind("reservationId") String reservationId, @Bind("eventTypes") Collection<String> eventTypes, @Bind("referenceDate") ZonedDateTime date); diff --git a/src/main/java/alfio/repository/BillingDocumentRepository.java b/src/main/java/alfio/repository/BillingDocumentRepository.java index 1053075640..adc77238b0 100644 --- a/src/main/java/alfio/repository/BillingDocumentRepository.java +++ b/src/main/java/alfio/repository/BillingDocumentRepository.java @@ -26,6 +26,11 @@ @QueryRepository public interface BillingDocumentRepository { + String LOAD_ALL_BY_EVENT_ID = "select a.* from billing_document a inner join" + + " (select max(generation_ts) as time,reservation_id_fk from billing_document where status = 'VALID' and type = 'INVOICE' and event_id_fk = :eventId group by reservation_id_fk) b" + + " on a.generation_ts = b.time and a.reservation_id_fk = b.reservation_id_fk" + + " union select * from billing_document where status = 'VALID' and event_id_fk = :eventId and type <> 'INVOICE'"; + @Query("select * from billing_document where reservation_id_fk = :reservationId and status = 'VALID' order by generation_ts desc limit 1") Optional<BillingDocument> findLatestByReservationId(@Bind("reservationId") String reservationId); @@ -62,15 +67,12 @@ AffectedRowCountAndKey<Long> insert(@Bind("eventId") Integer eventId, " on a.generation_ts = b.time and a.reservation_id_fk = b.reservation_id_fk") List<BillingDocument> findAllOfTypeForEvent(@Bind("type") BillingDocument.Type type, @Bind("eventId") int eventId); - @Query( - "select a.* from billing_document a inner join " + - "(select max(generation_ts) as time,reservation_id_fk from billing_document where status = 'VALID' and type = 'INVOICE' and event_id_fk = :eventId group by reservation_id_fk) b" + - " on a.generation_ts = b.time and a.reservation_id_fk = b.reservation_id_fk" + - " union select * from billing_document where status = 'VALID' and event_id_fk = :eventId and type <> 'INVOICE'" + - " order by reservation_id_fk, generation_ts" - ) + @Query(LOAD_ALL_BY_EVENT_ID + " order by reservation_id_fk, generation_ts") List<BillingDocument> findAllForEvent(@Bind("eventId") int eventId); + @Query("select count(*) from (" + LOAD_ALL_BY_EVENT_ID + ") docs") + Integer countAllForEvent(@Bind("eventId") int eventId); + @Query("delete from billing_document where reservation_id_fk = :reservationId") int deleteForReservation(@Bind("reservationId") String reservationId); diff --git a/src/main/java/alfio/repository/EventAdminRepository.java b/src/main/java/alfio/repository/EventAdminRepository.java index 1f44b84aff..d64d7386be 100644 --- a/src/main/java/alfio/repository/EventAdminRepository.java +++ b/src/main/java/alfio/repository/EventAdminRepository.java @@ -37,7 +37,7 @@ public interface EventAdminRepository { */ default boolean existsBySlug(String slug) { var jdbcTemplate = getJdbcTemplate(); - boolean rlsEnabled = Boolean.TRUE.equals(jdbcTemplate.queryForObject("select coalesce(current_setting('alfio.checkRowAccess', true), 'false')", EmptySqlParameterSource.INSTANCE, Boolean.class)); + boolean rlsEnabled = Boolean.TRUE.equals(jdbcTemplate.queryForObject("select coalesce(current_setting('alfio.checkRowAccess', true), 'false') = 'true'", EmptySqlParameterSource.INSTANCE, Boolean.class)); if (rlsEnabled) { jdbcTemplate.queryForObject("select set_config('alfio.checkRowAccess', 'false', true)", EmptySqlParameterSource.INSTANCE, Boolean.class); } diff --git a/src/main/java/alfio/repository/EventDescriptionRepository.java b/src/main/java/alfio/repository/EventDescriptionRepository.java index 5e7ef99dfb..9f244d2d63 100644 --- a/src/main/java/alfio/repository/EventDescriptionRepository.java +++ b/src/main/java/alfio/repository/EventDescriptionRepository.java @@ -36,11 +36,11 @@ public interface EventDescriptionRepository { List<EventDescription.LocaleDescription> findDescriptionByEventId(@Bind("eventId") int eventId); default Map<String, String> findByEventIdAsMap(int eventId) { - return findByEventId(eventId).stream().collect(Collectors.toMap(EventDescription::getLocale, EventDescription::getDescription)); + return findByEventId(eventId).stream().collect(Collectors.toMap(EventDescription::locale, EventDescription::description)); } default Map<String, String> findDescriptionByEventIdAsMap(int eventId) { - return findDescriptionByEventId(eventId).stream().collect(Collectors.toMap(EventDescription.LocaleDescription::getLocale, EventDescription.LocaleDescription::getDescription)); + return findDescriptionByEventId(eventId).stream().collect(Collectors.toMap(EventDescription.LocaleDescription::locale, EventDescription.LocaleDescription::description)); } @Query("select description from event_description_text where event_id_fk = :eventId and type = :type and locale = :locale") diff --git a/src/main/java/alfio/repository/EventRepository.java b/src/main/java/alfio/repository/EventRepository.java index e5351ca83e..50cef66a80 100644 --- a/src/main/java/alfio/repository/EventRepository.java +++ b/src/main/java/alfio/repository/EventRepository.java @@ -21,9 +21,6 @@ import alfio.model.metadata.AlfioMetadata; import alfio.model.support.JSONData; import ch.digitalfondue.npjt.*; -import org.springframework.jdbc.core.namedparam.EmptySqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.jdbc.core.simple.SimpleJdbcCall; import java.math.BigDecimal; import java.time.ZoneId; @@ -180,9 +177,13 @@ int updatePrices(@Bind("currency") String currency, @Query("select available_seats from events_statistics where id = :eventId") Integer countExistingTickets(@Bind("eventId") int eventId); + @Query("select count(*) from event") + Integer countEvents(); @Query(value = "update event set status = 'DISABLED' where org_id in (select org_id from j_user_organization where user_id in (:userIds)) returning id", type = QueryType.MODIFYING_WITH_RETURN) List<Integer> disableEventsForUsers(@Bind("userIds") Collection<Integer> userIds); + @Query(value = "update event set status = 'DISABLED' where org_id = :orgId returning id", type = QueryType.MODIFYING_WITH_RETURN) + List<Integer> disableEventsForOrganization(@Bind("orgId") int orgId); @Query("select coalesce(sum(final_price_cts),0) from tickets_reservation where event_id_fk = :eventId and status = 'COMPLETE'") long getGrossIncome(@Bind("eventId") int eventId); @@ -212,4 +213,8 @@ List<Event> findVisibleBySearchOptions(@Bind("subscriptionId") UUID subscription @Bind("organizer") Integer organizer, @Bind("organizerSlug") String organizerSlug, @Bind("tags") List<String> tags); + + @Query("select id from event where short_name in (:shortNames) and org_id = :orgId") + List<Integer> findIdsByShortNames(@Bind("shortNames") List<String> eventShortNames, + @Bind("orgId") int organizationId); } diff --git a/src/main/java/alfio/repository/ExportRepository.java b/src/main/java/alfio/repository/ExportRepository.java new file mode 100644 index 0000000000..31ca8a6873 --- /dev/null +++ b/src/main/java/alfio/repository/ExportRepository.java @@ -0,0 +1,85 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.repository; + +import alfio.model.ReservationsByEvent; +import ch.digitalfondue.npjt.Bind; +import ch.digitalfondue.npjt.Query; +import ch.digitalfondue.npjt.QueryRepository; +import ch.digitalfondue.npjt.QueryType; + +import java.time.ZonedDateTime; +import java.util.List; + +@QueryRepository +public interface ExportRepository { + @Query(type = QueryType.SELECT, value = "with tickets as (" + + " select tr_id, jsonb_agg(jsonb_build_object(" + + " 'id', t_uuid," + + " 'firstName', t_first_name," + + " 'lastName', t_last_name," + + " 'type', tc_name," + + " 'status', t_status," + + " 'srcPriceCts', t_src_price_cts," + + " 'taxCts', t_vat_cts," + + " 'taxStatus', t_vat_status," + + " 'finalPriceCts', t_final_price_cts" + + " )) items" + + " from checkin_ticket_event_and_category_info" + + " group by 1)," + + " reservations as (" + + " select distinct event_id_fk, id, invoice_number, first_name," + + " billing_address_company, vat_nr, vat_country," + + " last_name, email_address, payment_method, currency_code, invoicing_additional_information#>>'{italianEInvoicing,fiscalCode}' as tax_code," + + " confirmation_ts, final_price_cts, src_price_cts, vat_cts, vat_status from tickets_reservation" + + " where status in ('OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT', 'COMPLETE', 'CANCELLED')" + + " and confirmation_ts between :startTs and :endTs" + + " and event_id_fk is not null" + + " )," + + " reservations_event as (" + + " select tr.event_id_fk e_id, jsonb_agg(jsonb_build_object(" + + " 'id', tr.id," + + " 'invoiceNumber', tr.invoice_number," + + " 'firstName', tr.first_name," + + " 'lastName', tr.last_name," + + " 'email', tr.email_address," + + " 'paymentType', tr.payment_method," + + " 'finalPriceCts', tr.final_price_cts," + + " 'currency', tr.currency_code," + + " 'taxId', tr.vat_nr," + + " 'taxCountry', tr.vat_country," + + " 'companyName', tr.billing_address_company," + + " 'srcPriceCts', tr.src_price_cts," + + " 'taxCts', tr.vat_cts," + + " 'taxStatus', tr.vat_status," + + " 'taxCode', tr.tax_code," + + " 'confirmationTimestamp', to_char(tr.confirmation_ts at time zone 'UTC', 'YYYY-MM-DD') || 'T' || to_char(tr.confirmation_ts at time zone 'UTC', 'HH24:MI:SS.MSZ')," + + " 'tickets', t.items" + + " )) as reservations" + + " from reservations tr" + + " join tickets t on t.tr_id = tr.id" + + " group by 1" + + " )" + + " select e.id as event_id, e.short_name as event_short_name, e.display_name as event_display_name, tr.reservations" + + " from event e" + + " join reservations_event tr on e.id = tr.e_id" + + " where e.org_id in (:orgIds)" + + " order by 1") + List<ReservationsByEvent> allReservationsForInterval(@Bind("startTs") ZonedDateTime from, + @Bind("endTs") ZonedDateTime to, + @Bind("orgIds") List<Integer> orgIds); +} diff --git a/src/main/java/alfio/repository/GroupRepository.java b/src/main/java/alfio/repository/GroupRepository.java index 1e1bb4f573..2f64f39fe7 100644 --- a/src/main/java/alfio/repository/GroupRepository.java +++ b/src/main/java/alfio/repository/GroupRepository.java @@ -28,6 +28,8 @@ import java.util.Optional; import java.util.UUID; +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4; + @QueryRepository public interface GroupRepository { @@ -66,7 +68,9 @@ AffectedRowCountAndKey<Integer> createConfiguration(@Bind("groupId") int groupId default int[] insert(int groupId, List<GroupMemberModification> members) { MapSqlParameterSource[] params = members.stream() - .map(i -> new MapSqlParameterSource("groupId", groupId).addValue("value", i.getValue().toLowerCase()).addValue("description", i.getDescription())) + .map(i -> new MapSqlParameterSource("groupId", groupId) + .addValue("value", i.getValue().toLowerCase()) + .addValue("description", escapeHtml4(i.getDescription()))) .toArray(MapSqlParameterSource[]::new); return getNamedParameterJdbcTemplate().batchUpdate("insert into group_member(a_group_id_fk, value, description) values(:groupId, :value, :description)", params); diff --git a/src/main/java/alfio/repository/OrganizationDeleterRepository.java b/src/main/java/alfio/repository/OrganizationDeleterRepository.java new file mode 100644 index 0000000000..c675ed6de5 --- /dev/null +++ b/src/main/java/alfio/repository/OrganizationDeleterRepository.java @@ -0,0 +1,123 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.repository; + +import ch.digitalfondue.npjt.Bind; +import ch.digitalfondue.npjt.Query; +import ch.digitalfondue.npjt.QueryRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +@QueryRepository +public interface OrganizationDeleterRepository { + + Logger LOGGER = LoggerFactory.getLogger(OrganizationDeleterRepository.class); + String SELECT_EMPTY_ORGANIZATIONS = "select distinct(org_id) from j_user_organization where org_id in(:organizationIds)"; + + @Query("delete from auditing where organization_id_fk in(:organizationIds)" + + " and organization_id_fk not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteAuditingForEmptyOrganizations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from invoice_sequences where organization_id_fk in(:organizationIds)" + + " and organization_id_fk not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteInvoiceSequencesForEmptyOrganizations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from a_group where organization_id_fk in(:organizationIds)" + + " and organization_id_fk not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteGroupsForEmptyOrganizations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from group_member where organization_id_fk in(:organizationIds)" + + " and organization_id_fk not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteGroupMembersForEmptyOrganizations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from configuration_organization where organization_id_fk in(:organizationIds)" + + " and organization_id_fk not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteConfigurationForEmptyOrganizations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from resource_organizer where organization_id_fk in(:organizationIds)" + + " and organization_id_fk not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteResourcesForEmptyOrganizations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from subscription where organization_id_fk in (:organizationIds)") + int deleteSubscriptions(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from subscription_descriptor where organization_id_fk in (:organizationIds)") + int deleteSubscriptionDescriptors(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from promo_code where organization_id_fk in (:organizationIds)") + int deletePromoCodes(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from organization where id in(:organizationIds)" + + " and id not in (" + SELECT_EMPTY_ORGANIZATIONS + ")") + int deleteOrganizationsIfEmpty(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from tickets_reservation where organization_id_fk in (:organizationIds)") + int deleteReservations(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from admin_reservation_request where organization_id_fk in (:organizationIds)") + int deleteAdminReservationRequests(@Bind("organizationIds") List<Integer> organizationIds); + + @Query("delete from email_message where organization_id_fk in (:organizationIds)") + int deleteEmailMessages(@Bind("organizationIds") List<Integer> organizationIds); + + default void deleteEmptyOrganizations(List<Integer> organizationIds) { + // delete invoice sequences + int deletedSequences = deleteInvoiceSequencesForEmptyOrganizations(organizationIds); + LOGGER.info("deleted {} invoice sequences", deletedSequences); + // delete auditing + int deletedAuditing = deleteAuditingForEmptyOrganizations(organizationIds); + LOGGER.info("deleted {} auditing rows", deletedAuditing); + // delete groups + int deletedGroupMembers = deleteGroupMembersForEmptyOrganizations(organizationIds); + int deletedGroups = deleteGroupsForEmptyOrganizations(organizationIds); + LOGGER.info("deleted {} groups and {} members", deletedGroups, deletedGroupMembers); + + // delete configuration + int deletedConfigurations = deleteConfigurationForEmptyOrganizations(organizationIds); + LOGGER.info("deleted {} configurations", deletedConfigurations); + + // delete resources + int deletedResources = deleteResourcesForEmptyOrganizations(organizationIds); + LOGGER.info("deleted {} resources", deletedResources); + + int deletedEmails = deleteEmailMessages(organizationIds); + LOGGER.info("deleted {} email messages", deletedEmails); + + // delete subscriptions + int deletedSubscriptions = deleteSubscriptions(organizationIds); + int deletedDescriptors = deleteSubscriptionDescriptors(organizationIds); + LOGGER.info("deleted {} subscription descriptors and {} subscriptions", deletedDescriptors, deletedSubscriptions); + + // delete all reservations + int deletedReservations = deleteReservations(organizationIds); + LOGGER.info("deleted {} reservations", deletedReservations); + + // delete admin reservation request + int deletedAdminReservationRequests = deleteAdminReservationRequests(organizationIds); + LOGGER.info("deleted {} adminReservationRequests", deletedAdminReservationRequests); + + // delete promo codes + int deletedPromoCodes = deletePromoCodes(organizationIds); + LOGGER.info("deleted {} promo codes", deletedPromoCodes); + + int deletedOrganizations = deleteOrganizationsIfEmpty(organizationIds); + LOGGER.info("deleted {} empty organizations", deletedOrganizations); + + } +} diff --git a/src/main/java/alfio/repository/PromoCodeDiscountRepository.java b/src/main/java/alfio/repository/PromoCodeDiscountRepository.java index 1b004ef63a..fd726451ae 100644 --- a/src/main/java/alfio/repository/PromoCodeDiscountRepository.java +++ b/src/main/java/alfio/repository/PromoCodeDiscountRepository.java @@ -17,6 +17,7 @@ package alfio.repository; import alfio.model.PromoCodeDiscount; +import alfio.model.PromoCodeUsageResult; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; import ch.digitalfondue.npjt.QueryRepository; @@ -44,8 +45,8 @@ public interface PromoCodeDiscountRepository { @Query("select * from promo_code where id = :id") Optional<PromoCodeDiscount> findOptionalById(@Bind("id") int id); - @Query("insert into promo_code(promo_code, event_id_fk, organization_id_fk, valid_from, valid_to, discount_amount, discount_type, categories, max_usage, description, email_reference, code_type, hidden_category_id) " - + " values (:promoCode, :eventId, :organizationId, :start, :end, :discountAmount, :discountType, :categories, :maxUsage, :description, :emailReference, :codeType, :hiddenCategoryId)") + @Query("insert into promo_code(promo_code, event_id_fk, organization_id_fk, valid_from, valid_to, discount_amount, discount_type, categories, max_usage, description, email_reference, code_type, hidden_category_id, currency_code) " + + " values (:promoCode, :eventId, :organizationId, :start, :end, :discountAmount, :discountType, :categories, :maxUsage, :description, :emailReference, :codeType, :hiddenCategoryId, :currencyCode)") int addPromoCode(@Bind("promoCode") String promoCode, @Bind("eventId") Integer eventId, @Bind("organizationId") int organizationId, @@ -58,7 +59,8 @@ int addPromoCode(@Bind("promoCode") String promoCode, @Bind("description") String description, @Bind("emailReference") String emailReference, @Bind("codeType") PromoCodeDiscount.CodeType codeType, - @Bind("hiddenCategoryId") Integer hiddenCategoryId); + @Bind("hiddenCategoryId") Integer hiddenCategoryId, + @Bind("currencyCode") String currencyCode); @Query("insert into promo_code(promo_code, event_id_fk, organization_id_fk, valid_from, valid_to, discount_amount, discount_type, categories, max_usage, description, email_reference, code_type, hidden_category_id) " + " values (:promoCode, :eventId, :organizationId, :start, :end, :discountAmount, :discountType, :categories, :maxUsage, :description, :emailReference, :codeType, :hiddenCategoryId) " @@ -113,4 +115,9 @@ int updateEventPromoCode(@Bind("id") int id, @Query("select id from promo_code where code_type = 'ACCESS' and id = :id for update") Integer lockAccessCodeForUpdate(@Bind("id") int id); + + @Query("select * from promocode_usage_details where promo_code = :promoCode" + + " and (:eventId is null or :eventId = event_id)") + List<PromoCodeUsageResult> findDetailedUsage(@Bind("promoCode") String promoCode, + @Bind("eventId") Integer eventId); } diff --git a/src/main/java/alfio/repository/SponsorScanRepository.java b/src/main/java/alfio/repository/SponsorScanRepository.java index 026ef1ac48..ceba579ccd 100644 --- a/src/main/java/alfio/repository/SponsorScanRepository.java +++ b/src/main/java/alfio/repository/SponsorScanRepository.java @@ -33,29 +33,31 @@ public interface SponsorScanRepository { ZonedDateTime DEFAULT_TIMESTAMP = ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC); - @Query("select creation from sponsor_scan where user_id = :userId and event_id = :eventId and ticket_id = :ticketId") - Optional<ZonedDateTime> getRegistrationTimestamp(@Bind("userId") int userId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId); + @Query("select creation from sponsor_scan where user_id = :userId and event_id = :eventId and ticket_id = :ticketId and operator = :operator") + Optional<ZonedDateTime> getRegistrationTimestamp(@Bind("userId") int userId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId, @Bind("operator") String operator); - @Query("insert into sponsor_scan (user_id, creation, event_id, ticket_id, notes, lead_status) values(:userId, :creation, :eventId, :ticketId, :notes, :leadStatus)") + @Query("insert into sponsor_scan (user_id, creation, event_id, ticket_id, notes, lead_status, operator) values(:userId, :creation, :eventId, :ticketId, :notes, :leadStatus, :operator)") int insert(@Bind("userId") int userId, @Bind("creation") ZonedDateTime creation, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId, @Bind("notes") String notes, - @Bind("leadStatus") SponsorScan.LeadStatus leadStatus); + @Bind("leadStatus") SponsorScan.LeadStatus leadStatus, + @Bind("operator") String operator); - @Query("update sponsor_scan set notes = :notes, lead_status = :leadStatus where user_id = :userId and event_id = :eventId and ticket_id = :ticketId") + @Query("update sponsor_scan set notes = :notes, lead_status = :leadStatus where user_id = :userId and event_id = :eventId and ticket_id = :ticketId and operator = :operator") int updateNotesAndLeadStatus(@Bind("userId") int userId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId, @Bind("notes") String notes, - @Bind("leadStatus") SponsorScan.LeadStatus leadStatus); + @Bind("leadStatus") SponsorScan.LeadStatus leadStatus, + @Bind("operator") String operator); @Query("select t.id t_id, t.uuid t_uuid, t.creation t_creation, t.category_id t_category_id, t.status t_status, t.event_id t_event_id," + " t.src_price_cts t_src_price_cts, t.final_price_cts t_final_price_cts, t.vat_cts t_vat_cts, t.discount_cts t_discount_cts, t.tickets_reservation_id t_tickets_reservation_id," + " t.full_name t_full_name, t.first_name t_first_name, t.last_name t_last_name, t.email_address t_email_address, t.locked_assignment t_locked_assignment," + " t.user_language t_user_language, t.ext_reference t_ext_reference, t.currency_code t_currency_code, t.tags t_tags, t.subscription_id_fk t_subscription_id, t.vat_status t_vat_status," + - " s.user_id s_user_id, s.creation s_creation, s.event_id s_event_id, s.ticket_id s_ticket_id, s.notes s_notes, s.lead_status s_lead_status, " + + " s.user_id s_user_id, s.creation s_creation, s.event_id s_event_id, s.ticket_id s_ticket_id, s.notes s_notes, s.lead_status s_lead_status, s.operator s_operator, " + " (case when s.lead_status = 'HOT' then 2 when s.lead_status = 'WARM' then 1 else 0 end) as priority"+ " from sponsor_scan s, ticket t where s.event_id = :eventId and s.user_id = :userId and s.creation > :start and s.ticket_id = t.id order by priority desc, s.creation") List<DetailedScanData> loadSponsorData(@Bind("eventId") int eventId, diff --git a/src/main/java/alfio/repository/SubscriptionRepository.java b/src/main/java/alfio/repository/SubscriptionRepository.java index 0982607cc5..200a7f0845 100644 --- a/src/main/java/alfio/repository/SubscriptionRepository.java +++ b/src/main/java/alfio/repository/SubscriptionRepository.java @@ -18,6 +18,7 @@ import alfio.model.AllocationStatus; import alfio.model.PriceContainer.VatStatus; +import alfio.model.metadata.SubscriptionMetadata; import alfio.model.subscription.*; import alfio.model.subscription.SubscriptionDescriptor.SubscriptionTimeUnit; import alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType; @@ -56,11 +57,11 @@ public interface SubscriptionRepository { @Query("insert into subscription_descriptor (" + "id, title, description, max_available, on_sale_from, on_sale_to, price_cts, vat, vat_status, currency, is_public, organization_id_fk, " + " max_entries, validity_type, validity_time_unit, validity_units, validity_from, validity_to, usage_type, terms_conditions_url, privacy_policy_url," + - " file_blob_id_fk, allowed_payment_proxies, private_key, time_zone, supports_tickets_generation) " + + " file_blob_id_fk, allowed_payment_proxies, private_key, time_zone, supports_tickets_generation, status) " + " values(:id, :title::jsonb, :description::jsonb, :maxAvailable, :onSaleFrom, :onSaleTo, :priceCts, :vat, :vatStatus::VAT_STATUS, :currency, " + " :isPublic, :organizationId, :maxEntries, :validityType::SUBSCRIPTION_VALIDITY_TYPE, :validityTimeUnit::SUBSCRIPTION_TIME_UNIT, " + " :validityUnits, :validityFrom, :validityTo, :usageType::SUBSCRIPTION_USAGE_TYPE, :tcUrl, :privacyPolicyUrl," + - " :fileBlobId, :allowedPaymentProxies::text[], :privateKey, :timeZone, :supportsTicketsGeneration)") + " :fileBlobId, :allowedPaymentProxies::text[], :privateKey, :timeZone, :supportsTicketsGeneration, 'ACTIVE')") int createSubscriptionDescriptor(@Bind("id") UUID id, @Bind("title") @JSONData Map<String, String> title, @Bind("description") @JSONData Map<String, String> description, @@ -130,36 +131,45 @@ int updateSubscriptionDescriptor(@Bind("title") @JSONData Map<String, String> ti @Query("update subscription_descriptor set is_public = :isPublic where id = :id and organization_id_fk = :organizationId") int setPublicStatus(@Bind("id") UUID id, @Bind("organizationId") int organizationId, @Bind("isPublic") boolean isPublic); - @Query("select * from subscription_descriptor where organization_id_fk = :organizationId order by on_sale_from, on_sale_to nulls last") + @Query("update subscription_descriptor set status = 'NOT_ACTIVE' where id = :id and organization_id_fk = :organizationId") + int deactivateDescriptor(@Bind("id") UUID id, @Bind("organizationId") int organizationId); + + @Query("select * from subscription_descriptor where organization_id_fk = :organizationId" + + " and status = 'ACTIVE'" + + " order by on_sale_from, on_sale_to nulls last") List<SubscriptionDescriptor> findAllByOrganizationIds(@Bind("organizationId") int organizationId); @Query("select subscription_descriptor.* from subscription_descriptor" + " join organization org on subscription_descriptor.organization_id_fk = org.id "+ - " where is_public = true and" + + " where status = 'ACTIVE' and is_public = true and" + " (max_entries > 0 or max_entries = -1) and (on_sale_from is null or :from >= on_sale_from) " + " and (on_sale_to is null or :from <= on_sale_to)" + " and (:orgSlug is null or org.slug = :orgSlug)"+ " order by on_sale_from, on_sale_to nulls last") List<SubscriptionDescriptor> findAllActiveAndPublic(@Bind("from") ZonedDateTime from, @Bind("orgSlug") String organizerSlug); - @Query("select * from subscription_descriptor where id = :id and organization_id_fk = :organizationId") + @Query("select * from subscription_descriptor where status = 'ACTIVE' and id = :id and organization_id_fk = :organizationId") Optional<SubscriptionDescriptor> findOne(@Bind("id") UUID id, @Bind("organizationId") int organizationId); - @Query("select * from subscription_descriptor where id = :id") + @Query("select * from subscription_descriptor where id = :id and status = 'ACTIVE'") Optional<SubscriptionDescriptor> findOne(@Bind("id") UUID id); - @Query("select * from subscription_descriptor where id = (select subscription_descriptor_fk from subscription where reservation_id_fk = :reservationId)") + @Query("select * from subscription_descriptor where id = (select subscription_descriptor_fk from subscription where reservation_id_fk = :reservationId) and status = 'ACTIVE'") Optional<SubscriptionDescriptor> findDescriptorByReservationId(@Bind("reservationId") String reservationId); - @Query("select * from subscription_descriptor where id = (select subscription_descriptor_fk from subscription where id = :id)") + @Query("select * from subscription_descriptor where id = (select subscription_descriptor_fk from subscription where id = :id) and status = 'ACTIVE'") SubscriptionDescriptor findDescriptorBySubscriptionId(@Bind("id") UUID subscriptionId); - @Query("select organization_id_fk from subscription_descriptor where id = (select subscription_descriptor_fk from subscription where id = :id)") + @Query("select organization_id_fk from subscription_descriptor where id = (select subscription_descriptor_fk from subscription where id = :id) and status = 'ACTIVE'") Optional<Integer> findOrganizationIdForSubscription(@Bind("id") UUID subscriptionId); @Query("select * from subscription_descriptor_statistics where sd_organization_id_fk = :organizationId") List<SubscriptionDescriptorWithStatistics> findAllWithStatistics(@Bind("organizationId") int organizationId); + @Query("select * from subscription_descriptor_statistics where sd_organization_id_fk = :organizationId and sd_id = :subscriptionDescriptorId") + Optional<SubscriptionDescriptorWithStatistics> findOneWithStatistics(@Bind("subscriptionDescriptorId") UUID subscriptionDescriptorId, + @Bind("organizationId") int organizationId); + @Query("select exists (select 1 from subscription_event where event_id_fk = :eventId" + " and subscription_descriptor_id_fk = :subscriptionDescriptorId::uuid and organization_id_fk = :organizationId)") boolean isSubscriptionLinkedToEvent(@Bind("eventId") int eventId, @@ -182,7 +192,8 @@ List<EventSubscriptionLink> findLinkedEvents(@Bind("organizationId") int organiz @Query("select subscription_descriptor_id_fk from subscription_event where event_id_fk = :eventId and organization_id_fk = :organizationId") List<UUID> findLinkedSubscriptionIds(@Bind("eventId") int eventId, @Bind("organizationId") int organizationId); - @Query("select * from subscription_descriptor where organization_id_fk = :organizationId and (on_sale_to is null or on_sale_to > now()) order by on_sale_from, on_sale_to nulls last") + @Query("select * from subscription_descriptor where status = 'ACTIVE' and organization_id_fk = :organizationId" + + " and (on_sale_to is null or on_sale_to > now()) order by on_sale_from, on_sale_to nulls last") List<SubscriptionDescriptor> findActiveSubscriptionsForOrganization(@Bind("organizationId") int organizationId); @Query("delete from subscription_event where event_id_fk = :eventId and organization_id_fk = :organizationId" + @@ -191,10 +202,20 @@ int removeStaleSubscriptions(@Bind("eventId") int eventId, @Bind("organizationId") int organizationId, @Bind("descriptorIds") List<UUID> currentDescriptors); + @Query("delete from subscription_event where subscription_descriptor_id_fk = :subscriptionId and organization_id_fk = :organizationId" + + " and event_id_fk not in (:eventIds)") + int removeStaleEvents(@Bind("subscriptionId") UUID subscriptionId, + @Bind("organizationId") int organizationId, + @Bind("eventIds") List<Integer> eventIds); + @Query("delete from subscription_event where event_id_fk = :eventId and organization_id_fk = :organizationId") int removeAllSubscriptionsForEvent(@Bind("eventId") int eventId, @Bind("organizationId") int organizationId); + @Query("delete from subscription_event where subscription_descriptor_id_fk = :subscriptionId and organization_id_fk = :organizationId") + int removeAllLinksForSubscription(@Bind("subscriptionId") UUID subscriptionId, + @Bind("organizationId") int organizationId); + @Query(type = QueryType.TEMPLATE, value = INSERT_SUBSCRIPTION) String batchCreateSubscription(); @@ -232,7 +253,7 @@ int createSubscription(@Bind("id") UUID id, @Query("select * from subscription where reservation_id_fk = :reservationId for update") Optional<Subscription> findFirstSubscriptionByReservationIdForUpdate(@Bind("reservationId") String reservationId); - @Query("select exists(select 1 from subscription_descriptor where id = :id)") + @Query("select exists(select 1 from subscription_descriptor where id = :id and status = 'ACTIVE')") boolean existsById(@Bind("id") UUID subscriptionId); @Query("select exists (select id from subscription_event where event_id_fk = :eventId)") @@ -343,7 +364,8 @@ default Map<Integer, List<AvailableSubscriptionsByEvent>> loadAvailableSubscript " and sd.supports_tickets_generation is TRUE " + " and exp.inception <= now() " + " and exp.expiration > now() " + - " and (s.max_usage = -1 or s.max_usage > u.usage) " + + " and (s.max_entries = -1 or s.max_entries > u.usage) " + + " and (select count(*) from ticket_category where event_id = e.id and tc_status = 'ACTIVE') > 0" + " order by e.id", paramSource, rse -> { int eId = rse.getInt("event_id"); if (!result.containsKey(eId)) { @@ -364,4 +386,14 @@ default Map<Integer, List<AvailableSubscriptionsByEvent>> loadAvailableSubscript @Query("update subscription set src_price_cts = :price where subscription_descriptor_fk = :descriptorId and status = 'FREE'") int updatePriceForSubscriptions(@Bind("descriptorId") UUID subscriptionDescriptorId, @Bind("price") int priceCts); + + @Query("update subscription set max_entries = :maxEntries, max_usage = :maxEntries where subscription_descriptor_fk = :descriptorId") + int updateMaxEntriesForSubscriptions(@Bind("descriptorId") UUID subscriptionDescriptorId, @Bind("maxEntries") int maxEntries); + + @Query("update subscription set metadata = :metadata::jsonb where id = :id") + int setMetadataForSubscription(@Bind("id") UUID subscriptionId, @Bind("metadata") @JSONData SubscriptionMetadata metadata); + + @Query("select metadata from subscription where id = :id") + @JSONData + SubscriptionMetadata getSubscriptionMetadata(@Bind("id") UUID subscriptionId); } diff --git a/src/main/java/alfio/repository/TicketCategoryRepository.java b/src/main/java/alfio/repository/TicketCategoryRepository.java index 09f1a9eb5b..bccd49a78d 100644 --- a/src/main/java/alfio/repository/TicketCategoryRepository.java +++ b/src/main/java/alfio/repository/TicketCategoryRepository.java @@ -34,6 +34,8 @@ @QueryRepository public interface TicketCategoryRepository { + String CHECK_ACTIVE = "event_id = :eventId and tc_status = 'ACTIVE'"; + @Query("insert into ticket_category(inception, expiration, name, max_tickets, price_cts, src_price_cts, access_restricted, tc_status, event_id, bounded, category_code, valid_checkin_from, valid_checkin_to, ticket_validity_start, ticket_validity_end, ordinal, ticket_checkin_strategy, metadata, ticket_access_type) " + "values(:inception, :expiration, :name, :max_tickets, 0, :price, :accessRestricted, 'ACTIVE', :eventId, :bounded, :code, :validCheckInFrom, :validCheckInTo, :ticketValidityStart, :ticketValidityEnd, :ordinal, :ticketCheckInStrategy, :metadata::jsonb, :ticketAccessType::ticket_access_type)") @AutoGeneratedKey("id") @@ -55,13 +57,13 @@ AffectedRowCountAndKey<Integer> insert(@Bind("inception") ZonedDateTime inceptio @Bind("metadata") @JSONData AlfioMetadata metadata, @Bind("ticketAccessType") TicketCategory.TicketAccessType ticketAccessType); - @Query("select * from ticket_category_with_currency where id = :id and event_id = :eventId and tc_status = 'ACTIVE'") + @Query("select * from ticket_category_with_currency where id = :id and " + CHECK_ACTIVE) TicketCategory getByIdAndActive(@Bind("id") int id, @Bind("eventId") int eventId); - @Query("select * from ticket_category_with_currency where id in (:ids) and event_id = :eventId and tc_status = 'ACTIVE'") + @Query("select * from ticket_category_with_currency where id in (:ids) and " + CHECK_ACTIVE) List<TicketCategory> getByIdsAndActive(@Bind("ids") Collection<Integer> ids, @Bind("eventId") int eventId); - @Query("select * from ticket_category_with_currency where id = :id and event_id = :eventId and tc_status = 'ACTIVE'") + @Query("select * from ticket_category_with_currency where id = :id and " + CHECK_ACTIVE) Optional<TicketCategory> getOptionalByIdAndActive(@Bind("id") int id, @Bind("eventId") int eventId); @Query("select * from ticket_category_with_currency where id = :id and tc_status = 'ACTIVE'") @@ -76,10 +78,10 @@ AffectedRowCountAndKey<Integer> insert(@Bind("inception") ZonedDateTime inceptio @Query("select * from ticket_category_with_currency where event_id = :eventId and category_code = :code and tc_status = 'ACTIVE'") Optional<TicketCategory> findCodeInEvent(@Bind("eventId") int eventId, @Bind("code") String code); - @Query("select count(*) from ticket_category_with_currency where event_id = :eventId and tc_status = 'ACTIVE' and bounded = false") + @Query("select count(*) from ticket_category_with_currency where " + CHECK_ACTIVE + " and bounded = false") Integer countUnboundedCategoriesByEventId(@Bind("eventId") int eventId); - @Query("select * from ticket_category_with_currency where event_id = :eventId and tc_status = 'ACTIVE' and bounded = false order by expiration desc") + @Query("select * from ticket_category_with_currency where " + CHECK_ACTIVE + " and bounded = false order by expiration desc") List<TicketCategory> findUnboundedOrderByExpirationDesc(@Bind("eventId") int eventId); @Query("select * from ticket_category_with_currency where event_id = :eventId and tc_status = 'ACTIVE' order by ordinal asc, inception asc, expiration asc, id asc") @@ -99,13 +101,13 @@ default Map<Integer, TicketCategory> findByEventIdAsMap(int eventId) { return findAllTicketCategories(eventId).stream().collect(Collectors.toMap(TicketCategory::getId, Function.identity())); } - @Query("select id, metadata from ticket_category where event_id = :eventId and tc_status = 'ACTIVE' order by ordinal asc, inception asc, expiration asc, id asc") + @Query("select id, metadata from ticket_category where " + CHECK_ACTIVE + " order by ordinal asc, inception asc, expiration asc, id asc") List<EntityIdAndMetadata> findMetadataForCategoriesInEvent(@Bind("eventId") int eventId); default Map<Integer, AlfioMetadata> findCategoryMetadataForEventGroupByCategoryId(int eventId) { return findMetadataForCategoriesInEvent(eventId).stream() - .filter(ei -> ei.getMetadata() != null) - .collect(Collectors.toMap(EntityIdAndMetadata::getId, EntityIdAndMetadata::getMetadata)); + .filter(ei -> ei.metadata() != null) + .collect(Collectors.toMap(EntityIdAndMetadata::id, EntityIdAndMetadata::metadata)); } @Query("select count(*) from ticket_category_with_currency where event_id = :eventId and access_restricted = true") @@ -183,4 +185,7 @@ default Map<Integer, TicketCategoryStatisticView> findStatisticsForEventIdByCate @Query("update ticket_category set ticket_access_type = :ticketAccessType::ticket_access_type where event_id = :eventId") void updateTicketAccessTypeForEvent(@Bind("eventId") int eventId, @Bind("ticketAccessType") TicketCategory.TicketAccessType ticketAccessType); + + @Query("select count(*) from ticket_category where " + CHECK_ACTIVE) + Integer countActiveByEventId(@Bind("eventId") int eventId); } diff --git a/src/main/java/alfio/repository/TicketFieldRepository.java b/src/main/java/alfio/repository/TicketFieldRepository.java index 656747489b..ff5ccf001b 100644 --- a/src/main/java/alfio/repository/TicketFieldRepository.java +++ b/src/main/java/alfio/repository/TicketFieldRepository.java @@ -27,6 +27,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; +import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; @@ -173,8 +174,21 @@ default Map<String, String> findAllValuesForTicketId(int ticketId) { default List<RestrictedValueStats> retrieveStats(int configurationId) { TicketFieldConfiguration configuration = findById(configurationId); - Map<String, Integer> valueStats = getValueStats(configurationId).stream().collect(Collectors.toMap(RestrictedValueStats.RestrictedValueCount::getName, RestrictedValueStats.RestrictedValueCount::getCount)); + Map<String, Integer> valueStats = getValueStats(configurationId).stream().collect(Collectors.toMap(RestrictedValueStats.RestrictedValueCount::name, RestrictedValueStats.RestrictedValueCount::count)); int total = valueStats.values().stream().mapToInt(i -> i).sum(); + if (configuration.isCountryField()) { + // no restricted values + var descComparator = Entry.<String, Integer>comparingByValue().reversed(); + return valueStats.entrySet().stream() + .sorted(descComparator) + .map(entry -> { + int count = entry.getValue(); + return new RestrictedValueStats(entry.getKey(), + count, + new BigDecimal(count).divide(new BigDecimal(total), 2, RoundingMode.HALF_UP).multiply(MonetaryUtil.HUNDRED).intValue() + ); + }).collect(Collectors.toList()); + } return configuration.getRestrictedValues().stream() .map(name -> { int count = valueStats.getOrDefault(name, 0); diff --git a/src/main/java/alfio/repository/TicketRepository.java b/src/main/java/alfio/repository/TicketRepository.java index 1d540f8f3a..aec894377d 100644 --- a/src/main/java/alfio/repository/TicketRepository.java +++ b/src/main/java/alfio/repository/TicketRepository.java @@ -17,12 +17,16 @@ package alfio.repository; import alfio.model.*; -import alfio.model.checkin.OnlineCheckInFullInfo; +import alfio.model.checkin.AttendeeSearchResultsCount; +import alfio.model.checkin.CheckInFullInfo; +import alfio.model.metadata.TicketMetadata; import alfio.model.metadata.TicketMetadataContainer; +import alfio.model.modification.AttendeeData; import alfio.model.poll.PollParticipant; import alfio.model.support.Array; import alfio.model.support.EnumTypeAsString; import alfio.model.support.JSONData; +import alfio.util.Json; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; import ch.digitalfondue.npjt.QueryRepository; @@ -47,6 +51,8 @@ public interface TicketRepository { String RESET_TICKET = " TICKETS_RESERVATION_ID = null, FULL_NAME = null, EMAIL_ADDRESS = null, SPECIAL_PRICE_ID_FK = null, LOCKED_ASSIGNMENT = false, USER_LANGUAGE = null, REMINDER_SENT = false, SRC_PRICE_CTS = 0, FINAL_PRICE_CTS = 0, VAT_CTS = 0, DISCOUNT_CTS = 0, FIRST_NAME = null, LAST_NAME = null, EXT_REFERENCE = null, TAGS = array[]::text[], VAT_STATUS = null, METADATA = '{}'::jsonb "; String RELEASE_TICKET_QUERY = "update ticket set status = 'RELEASED', uuid = :newUuid, " + RESET_TICKET + " where id = :ticketId and status in('ACQUIRED', 'PENDING', 'TO_BE_PAID') and tickets_reservation_id = :reservationId and event_id = :eventId"; + String FIND_BASIC_TICKET_INFO_BY_EVENT_ID = "select t.id t_id, t.uuid t_uuid, tc.id tc_id, tc.bounded tc_bounded, t.vat_status t_vat_status from ticket t inner join ticket_category tc on t.category_id = tc.id where t.event_id = :eventId"; + String UPDATE_TICKET_PRICE = "update ticket set src_price_cts = :srcPriceCts, final_price_cts = :finalPriceCts, vat_cts = :vatCts, discount_cts = :discountCts, currency_code = :currencyCode, vat_status = :vatStatus::VAT_STATUS where event_id = :eventId and category_id = :categoryId"; //TODO: refactor, try to move the MapSqlParameterSource inside the default method! @@ -114,17 +120,27 @@ default int reserveTickets(String reservationId, TicketCategory category, String userLanguage, PriceContainer.VatStatus vatStatus, - IntFunction<String> ticketMetadataSupplier) { + IntFunction<AttendeeData> attendeeDataSupplier) { var idx = new AtomicInteger(); var batchReserveParameters = ticketIds.stream() - .map(id -> new MapSqlParameterSource("reservationId", reservationId) - .addValue("id", id) - .addValue("categoryId", category.getId()) - .addValue("userLanguage", userLanguage) - .addValue("srcPriceCts", category.getSrcPriceCts()) - .addValue("currencyCode", category.getCurrencyCode()) - .addValue("ticketMetadata", Objects.requireNonNullElse(ticketMetadataSupplier.apply(idx.getAndIncrement()), "{}")) - .addValue("vatStatus", vatStatus.name())).toArray(MapSqlParameterSource[]::new); + .map(id -> { + var attendee = Objects.requireNonNullElse(attendeeDataSupplier.apply(idx.getAndIncrement()), AttendeeData.empty()); + String metadata = null; + if (attendee.hasMetadata()) { + metadata = Json.toJson(TicketMetadataContainer.fromMetadata(new TicketMetadata(null, null, attendee.getMetadata()))); + } + return new MapSqlParameterSource("reservationId", reservationId) + .addValue("id", id) + .addValue("categoryId", category.getId()) + .addValue("userLanguage", userLanguage) + .addValue("srcPriceCts", category.getSrcPriceCts()) + .addValue("currencyCode", category.getCurrencyCode()) + .addValue("ticketMetadata", Objects.requireNonNullElse(metadata, "{}")) + .addValue("firstName", attendee.getFirstName()) + .addValue("lastName", attendee.getLastName()) + .addValue("email", attendee.getEmail()) + .addValue("vatStatus", vatStatus.name()); + }).toArray(MapSqlParameterSource[]::new); return (int) Arrays.stream(getNamedParameterJdbcTemplate().batchUpdate(batchReserveTickets(), batchReserveParameters)) .asLongStream() .sum(); @@ -133,14 +149,15 @@ default int reserveTickets(String reservationId, @Query(type = QueryType.TEMPLATE, value = "update ticket set tickets_reservation_id = :reservationId, status = 'PENDING', category_id = :categoryId, " + "user_language = :userLanguage, src_price_cts = :srcPriceCts, currency_code = :currencyCode, metadata = :ticketMetadata::jsonb, " + - "vat_status = :vatStatus::VAT_STATUS where id = :id") + "vat_status = :vatStatus::VAT_STATUS, first_name = :firstName, last_name = :lastName, email_address = :email where id = :id") String batchReserveTickets(); @Query(type = QueryType.TEMPLATE, value = "update ticket set tickets_reservation_id = :reservationId, special_price_id_fk = :specialCodeId," + " user_language = :userLanguage, status = 'PENDING', src_price_cts = :srcPriceCts," + " currency_code = :currencyCode, vat_status = :vatStatus::VAT_STATUS," + - " metadata = :ticketMetadata::jsonb where id = :ticketId") + " metadata = :ticketMetadata::jsonb, first_name = :firstName, last_name = :lastName, email_address = :email" + + " where id = :ticketId") String batchReserveTicketsForSpecialPrice(); @Query("update ticket set tickets_reservation_id = :reservationId, special_price_id_fk = :specialCodeId," + @@ -188,11 +205,13 @@ int updateTicketPrice(@Bind("ids") List<Integer> ids, @Bind("currencyCode") String currencyCode, @Bind("vatStatus") @EnumTypeAsString PriceContainer.VatStatus vatStatus); - @Query(type = QueryType.TEMPLATE, value = "update ticket set src_price_cts = :srcPriceCts, final_price_cts = :finalPriceCts," + - " vat_cts = :vatCts, discount_cts = :discountCts, currency_code = :currencyCode," + - " vat_status = :vatStatus::VAT_STATUS where event_id = :eventId and category_id = :categoryId and tickets_reservation_id = :reservationId") + @Query(type = QueryType.TEMPLATE, value = UPDATE_TICKET_PRICE + + " and tickets_reservation_id = :reservationId") String updateTicketPriceForCategoryInReservation(); + @Query(type = QueryType.TEMPLATE, value = UPDATE_TICKET_PRICE + " and uuid = :uuid") + String bulkUpdateTicketPrice(); + @Query("update ticket set tags = :tags::text[] where id in(:ids)") int updateTicketTags(@Bind("ids") List<Integer> ticketIds, @Bind("tags") @Array List<String> tags); @@ -245,6 +264,9 @@ int updateTicketPrice(@Bind("ids") List<Integer> ids, @Query("update ticket set email_address = :email, full_name = :fullName, first_name = :firstName, last_name = :lastName where id = :id") int updateTicketOwnerById(@Bind("id") int id, @Bind("email") String email, @Bind("fullName") String fullName, @Bind("firstName") String firstName, @Bind("lastName") String lastName); + @Query(type = QueryType.TEMPLATE, value = "update ticket set email_address = :email, full_name = :fullName, first_name = :firstName, last_name = :lastName, metadata = :metadata::jsonb where id = :id") + String updateTicketOwnerAndMetadataById(); + @Query("update ticket set locked_assignment = :lockedAssignment where id = :id and category_id = :categoryId") int toggleTicketLocking(@Bind("id") int ticketId, @Bind("categoryId") int categoryId, @Bind("lockedAssignment") boolean locked); @@ -360,14 +382,12 @@ default void resetTickets(List<Integer> ticketIds) { @Query("select count(*) from ticket where status = 'RELEASED' and event_id = :eventId") Integer countWaiting(@Bind("eventId") int eventId); - @Query("select " + - " t.id t_id, " + - " tc.id tc_id, tc.bounded tc_bounded " + - " from ticket t " + - " inner join ticket_category tc on t.category_id = tc.id "+ - " where t.event_id = :eventId and t.status = 'RELEASED' and tc.expiration <= :currentTs") + @Query(FIND_BASIC_TICKET_INFO_BY_EVENT_ID + " and t.status = 'RELEASED' and tc.expiration <= :currentTs") List<TicketInfo> findReleasedBelongingToExpiredCategories(@Bind("eventId") int eventId, @Bind("currentTs") ZonedDateTime now); + @Query(FIND_BASIC_TICKET_INFO_BY_EVENT_ID + " and t.tickets_reservation_id = :reservationId") + List<TicketInfo> findBasicTicketInfoForReservation(@Bind("eventId") int eventId, @Bind("reservationId") String reservationId); + @Query(REVERT_TO_FREE) int revertToFree(@Bind("eventId") int eventId); @@ -406,7 +426,23 @@ default void preReserveTicket(List<Integer> ids) { List<Integer> getCategoriesIdToPayInReservation(@Bind("reservationId") String reservationId); @Query("select * from checkin_ticket_event_and_category_info where t_uuid = :ticketUUID and e_short_name = :eventShortName and (e_format = 'ONLINE' or tc_ticket_access_type = 'ONLINE') ") - Optional<OnlineCheckInFullInfo> getFullInfoForOnlineCheckin(@Bind("eventShortName") String eventShortName, @Bind("ticketUUID") String ticketUUID); + Optional<CheckInFullInfo> getFullInfoForOnlineCheckin(@Bind("eventShortName") String eventShortName, @Bind("ticketUUID") String ticketUUID); + + @Query("select * from checkin_ticket_event_and_category_info where e_id = :eventId " + + "and (" + TicketSearchRepository.BASE_FILTER + " or lower(tc_name) like lower(:search)) "+ + "and (e_format = 'IN_PERSON' or tc_ticket_access_type = 'IN_PERSON') " + + "order by t_last_name, t_first_name, t_uuid limit :limit offset :offset") + List<CheckInFullInfo> searchAttendees(@Bind("eventId") int eventId, + @Bind("search") String search, + @Bind("limit") int limit, + @Bind("offset") int offset); + + @Query("select count(*) as total, count(*) filter (where t_status = 'CHECKED_IN') as checked_in " + + "from checkin_ticket_event_and_category_info where e_id = :eventId " + + "and (" + TicketSearchRepository.BASE_FILTER + " or lower(tc_name) like lower(:search)) "+ + "and (e_format = 'IN_PERSON' or tc_ticket_access_type = 'IN_PERSON') ") + AttendeeSearchResultsCount countSearchResults(@Bind("eventId") int eventId, + @Bind("search") String search); @Query("update ticket set status = 'CHECKED_IN', locked_assignment = true where uuid = :uuid and event_id = :eventId and status = 'ACQUIRED'") int performCheckIn(@Bind("uuid") String ticketUUID, @Bind("eventId") int eventId); @@ -447,4 +483,12 @@ int applySubscriptionToTicketsInReservation(@Bind("reservationId") String reserv @Query(FIND_TICKETS_IN_RESERVATION) List<TicketWithMetadataAttributes> findTicketsInReservationWithMetadata(@Bind("reservationId") String reservationId); + @Query("select t.id from ticket t" + + " join event e on t.event_id = e.id" + + " join tickets_reservation tr on t.tickets_reservation_id = tr.id" + + " join ticket_field_value tfv on t.id = tfv.ticket_id_fk" + + " where e.short_name = :eventPublicIdentifier and tr.id = :reservationId") + List<Integer> findTicketsWithAdditionalData(@Bind("reservationId") String reservationId, + @Bind("eventPublicIdentifier") String eventPublicIdentifier); + } diff --git a/src/main/java/alfio/repository/TicketReservationRepository.java b/src/main/java/alfio/repository/TicketReservationRepository.java index d95a2880ae..301131c17f 100644 --- a/src/main/java/alfio/repository/TicketReservationRepository.java +++ b/src/main/java/alfio/repository/TicketReservationRepository.java @@ -17,6 +17,7 @@ package alfio.repository; import alfio.model.*; +import alfio.model.support.JSONData; import alfio.model.support.UserIdAndOrganizationId; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; @@ -147,9 +148,6 @@ int postponePayment(@Bind("reservationId") String reservationId, @Bind("status") @Query("update tickets_reservation set invoice_number = :invoiceNumber where id = :reservationId") int setInvoiceNumber(@Bind("reservationId") String reservationId, @Bind("invoiceNumber") String invoiceNumber); - @Query("select count(*) from tickets_reservation where invoice_number is not null and event_id_fk = :eventId") - Integer countInvoices(@Bind("eventId") int eventId); - @Query("update tickets_reservation set vat_status = :vatStatus, src_price_cts = :srcPriceCts, " + " final_price_cts = :finalPriceCts, vat_cts = :vatCts, discount_cts = :discountCts, currency_code = :currencyCode, " + @@ -235,6 +233,16 @@ int updateTicketReservationWithValidation(@Bind("reservationId") String reservat " from tickets_reservation where id = :id") TicketReservationAdditionalInfo getAdditionalInfo(@Bind("id") String reservationId); + @Query("select metadata from tickets_reservation where id = :id") + @JSONData + ReservationMetadata getMetadata(@Bind("id") String reservationId); + + @Query("select metadata->'finalized' = 'true' as finalized from tickets_reservation where id = :id") + Boolean checkIfFinalized(@Bind("id") String reservationId); + + @Query("update tickets_reservation set metadata = :metadata::jsonb where id = :id") + int setMetadata(@Bind("id") String reservationId, @Bind("metadata") @JSONData ReservationMetadata metadata); + @Query("update tickets_reservation set validated_for_overview = :validated where id = :reservationId") int updateValidationStatus(@Bind("reservationId") String reservationId, @Bind("validated") boolean validated); diff --git a/src/main/java/alfio/repository/TicketSearchRepository.java b/src/main/java/alfio/repository/TicketSearchRepository.java index 6e00ddbe93..a71602595e 100644 --- a/src/main/java/alfio/repository/TicketSearchRepository.java +++ b/src/main/java/alfio/repository/TicketSearchRepository.java @@ -16,11 +16,13 @@ */ package alfio.repository; +import alfio.model.ReservationPaymentDetail; import alfio.model.TicketReservation; import alfio.model.TicketReservationWithTransaction; import alfio.model.TicketWithReservationAndTransaction; import alfio.model.poll.PollParticipant; import alfio.model.support.Array; +import alfio.model.transaction.Transaction; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; import ch.digitalfondue.npjt.QueryRepository; @@ -31,8 +33,12 @@ @QueryRepository public interface TicketSearchRepository { - String APPLY_FILTER = " (:search is null or (lower(tr_id) like lower(:search) or lower(t_uuid) like lower(:search) or lower(t_full_name) like lower(:search) or lower(t_first_name) like lower(:search) or lower(t_last_name) like lower(:search) or lower(t_email_address) like lower(:search) or " + - " lower(tr_full_name) like lower(:search) or lower(tr_first_name) like lower(:search) or lower(tr_last_name) like lower(:search) or lower(tr_email_address) like lower(:search) or lower(tr_customer_reference) like lower(:search) or lower(promo_code) like lower(:search) or lower(special_price_token) like lower(:search))) "; + + String BASE_FILTER = ":search is null or (lower(tr_id) like lower(:search) or lower(t_uuid) like lower(:search) or lower(t_full_name) like lower(:search) or lower(t_first_name) like lower(:search) or lower(t_last_name) like lower(:search) or lower(t_email_address) like lower(:search) or " + + " lower(tr_full_name) like lower(:search) or lower(tr_first_name) like lower(:search) or lower(tr_last_name) like lower(:search) or lower(tr_email_address) like lower(:search) or lower(tr_customer_reference) like lower(:search) " + + " or (tr_invoice_number is not null and lower(tr_invoice_number) like lower(:search)) )"; + + String APPLY_FILTER = " (" + BASE_FILTER + " or lower(promo_code) like lower(:search) or lower(special_price_token) like lower(:search)) "; String APPLY_FILTER_SUBSCRIPTION = " (:search is null or (lower(tr_id) like lower(:search) or lower(s_id::text) like lower(:search) or lower(s_first_name) like lower(:search) or lower(s_last_name) like lower(:search) or lower(s_email_address) like lower(:search) " + " or lower(tr_first_name) like lower(:search) or lower(tr_last_name) like lower(:search) or lower(tr_email_address) like lower(:search) or lower(tr_customer_reference) like lower(:search) or lower(promo_code) like lower(:search) )) "; @@ -41,6 +47,8 @@ public interface TicketSearchRepository { String FIND_ALL_CONFIRMED_TICKETS_FOR_EVENT = "select * from reservation_and_ticket_and_tx where t_id is not null and t_status in ('ACQUIRED', 'TO_BE_PAID', 'CHECKED_IN') and t_event_id = :eventId and " + APPLY_FILTER; + String FIND_ALL_PAYMENTS_FOR_EVENT = "select * from reservation_and_ticket_and_tx where tr_status in (:reservationStatus) and tr_payment_method in (:paymentMethods) and t_id is not null and t_status in ('ACQUIRED', 'TO_BE_PAID', 'CHECKED_IN') and t_event_id = :eventId and " + APPLY_FILTER; + String FIND_ALL_TICKETS_INCLUDING_NEW = "select * from reservation_and_ticket_and_tx where tr_event_id = :eventId and tr_id is not null and tr_status in (:status) and " + APPLY_FILTER; String FIND_ALL_SUBSCRIPTION_INCLUDING_NEW = "select * from reservation_and_subscription_and_tx where s_descriptor_id = :subscriptionDescriptorId::uuid and tr_id is not null and tr_status in (:status) and " + APPLY_FILTER_SUBSCRIPTION; @@ -89,6 +97,22 @@ List<TicketReservation> findReservationsForEvent(@Bind("eventId") int eventId, @Bind("search") String search, @Bind("status") List<String> toFilter); + @Query("select distinct tr_id, tr_first_name, tr_last_name, tr_email_address, tr_payment_method, bt_price_cts, bt_currency, bt_t_timestamp, bt_metadata ->> '"+ Transaction.NOTES_KEY + "'" + + " as bt_notes, tr_invoice_number from (" + FIND_ALL_PAYMENTS_FOR_EVENT + ") as d_tbl order by bt_t_timestamp desc nulls last limit :pageSize offset :page") + List<ReservationPaymentDetail> findAllPaymentsForEvent(@Bind("eventId") int eventId, + @Bind("page") int page, + @Bind("pageSize") int pageSize, + @Bind("search") String search, + @Bind("reservationStatus") List<String> toFilter, + @Bind("paymentMethods") List<String> paymentMethods); + + @Query("select distinct tr_id, tr_first_name, tr_last_name, tr_email_address, tr_payment_method, bt_price_cts, bt_currency, bt_t_timestamp, bt_metadata ->> '"+ Transaction.NOTES_KEY + "'" + + " as bt_notes, tr_invoice_number from (" + FIND_ALL_PAYMENTS_FOR_EVENT + ") as d_tbl order by bt_t_timestamp desc nulls last") + List<ReservationPaymentDetail> findAllEventPaymentsForExport(@Bind("eventId") int eventId, + @Bind("search") String search, + @Bind("reservationStatus") List<String> toFilter, + @Bind("paymentMethods") List<String> paymentMethods); + @Query("select distinct "+RESERVATION_FIELDS+" from (" + FIND_ALL_SUBSCRIPTION_INCLUDING_NEW + ") as d_tbl order by tr_confirmation_ts desc nulls last, tr_validity limit :pageSize offset :page") List<TicketReservation> findReservationsForSubscription(@Bind("subscriptionDescriptorId") UUID subscriptionDescriptorId, @Bind("page") int page, @@ -118,6 +142,12 @@ Integer countReservationsForSubscription(@Bind("subscriptionDescriptorId") UUID @Bind("search") String search, @Bind("status") List<String> toFilter); + @Query("select count(distinct tr_id) from (" + FIND_ALL_PAYMENTS_FOR_EVENT +") as d_tbl") + Integer countConfirmedPaymentsForEvent(@Bind("eventId") int eventId, + @Bind("search") String search, + @Bind("reservationStatus") List<String> toFilter, + @Bind("paymentMethods") List<String> paymentMethods); + @Query("select * from reservation_and_ticket_and_tx where tr_event_id = :eventId and tickets_count > 0 and tr_id in (:reservationIds)") List<TicketWithReservationAndTransaction> loadAllReservationsWithTickets(@Bind("eventId") int eventId, @Bind("reservationIds") Collection<String> reservationIds); diff --git a/src/main/java/alfio/repository/TransactionRepository.java b/src/main/java/alfio/repository/TransactionRepository.java index 021a93ab03..2fefe9a809 100644 --- a/src/main/java/alfio/repository/TransactionRepository.java +++ b/src/main/java/alfio/repository/TransactionRepository.java @@ -122,4 +122,9 @@ int updateFees(@Bind("transactionId") String transactionId, @Query("update b_transaction set status = 'INVALID' where id = :id") int invalidateById(@Bind("id") int id); + + @Query("update b_transaction set metadata = :metadata::jsonb, t_timestamp = :timestamp where id = :id") + int updateDetailsById(@Bind("id") int id, + @Bind("metadata") @JSONData Map<String, String> metadata, + @Bind("timestamp") ZonedDateTime timestamp); } diff --git a/src/main/java/alfio/repository/audit/ScanAuditRepository.java b/src/main/java/alfio/repository/audit/ScanAuditRepository.java index eca3813687..fce7a42d31 100644 --- a/src/main/java/alfio/repository/audit/ScanAuditRepository.java +++ b/src/main/java/alfio/repository/audit/ScanAuditRepository.java @@ -17,6 +17,7 @@ package alfio.repository.audit; import alfio.manager.support.CheckInStatus; +import alfio.model.api.v1.admin.CheckInLogEntry; import alfio.model.audit.ScanAudit; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; @@ -38,4 +39,12 @@ Integer insert(@Bind("ticketUuid") String ticketUuid, @Query("select * from scan_audit where event_id_fk = :eventId") List<ScanAudit> findAllForEvent(@Bind("eventId") int eventId); + @Query("select t.uuid t_uuid, jsonb_build_object('firstName', t.first_name, 'lastName', t.last_name, 'email', t.email_address, 'metadata', coalesce(t.metadata::jsonb#>'{metadataMap, general, attributes}', '{}')) attendee_data, jsonb_agg(jsonb_build_object('ticketUuid', sa.ticket_uuid, 'scanTimestamp', sa.scan_ts, 'username', sa.username, 'checkInStatus', sa.check_in_status, 'operation', sa.operation)) scans from ticket t\n" + + " join event e on t.event_id = e.id" + + " join scan_audit sa on e.id = sa.event_id_fk and t.uuid = sa.ticket_uuid" + + " where e.id = :eventId" + + " and t.status = 'CHECKED_IN'" + + " group by 1,2") + List<CheckInLogEntry> loadEntries(@Bind("eventId") int eventId); + } \ No newline at end of file diff --git a/src/main/java/alfio/repository/system/AdminJobQueueRepository.java b/src/main/java/alfio/repository/system/AdminJobQueueRepository.java index 919eeba362..38d285ce11 100644 --- a/src/main/java/alfio/repository/system/AdminJobQueueRepository.java +++ b/src/main/java/alfio/repository/system/AdminJobQueueRepository.java @@ -17,7 +17,6 @@ package alfio.repository.system; import alfio.manager.system.AdminJobExecutor.JobName; -import alfio.model.support.EnumTypeAsString; import alfio.model.support.JSONData; import alfio.model.system.AdminJobSchedule; import ch.digitalfondue.npjt.Bind; @@ -49,12 +48,13 @@ int updateSchedule(@Bind("id") long id, int scheduleRetry(@Bind("id") long id, @Bind("requestTs") ZonedDateTime requestTs); - @Query("insert into admin_job_queue(job_name, request_ts, metadata, status, attempts)" + - " values(:jobName, :requestTs, to_json(:metadata::json), 'SCHEDULED', 1)" + + @Query("insert into admin_job_queue(job_name, request_ts, metadata, status, attempts, allow_duplicates)" + + " values(:jobName, :requestTs, to_json(:metadata::json), 'SCHEDULED', 1, :allowDuplicates)" + " on conflict do nothing") int schedule(@Bind("jobName") JobName jobName, @Bind("requestTs") ZonedDateTime requestTimestamp, - @Bind("metadata") @JSONData Map<String, Object> metadata); + @Bind("metadata") @JSONData Map<String, Object> metadata, + @Bind("allowDuplicates") String allowDuplicates); @Query("delete from admin_job_queue where status in (:status) and request_ts <= :requestTs") int removePastSchedules(@Bind("requestTs") ZonedDateTime requestTs, @Bind("status") Set<String> statuses); diff --git a/src/main/java/alfio/repository/system/ConfigurationRepository.java b/src/main/java/alfio/repository/system/ConfigurationRepository.java index 3184abdbc1..e1bf4968d6 100644 --- a/src/main/java/alfio/repository/system/ConfigurationRepository.java +++ b/src/main/java/alfio/repository/system/ConfigurationRepository.java @@ -24,7 +24,7 @@ import ch.digitalfondue.npjt.Query; import ch.digitalfondue.npjt.QueryRepository; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; -import lombok.Getter; +import ch.digitalfondue.npjt.QueryType; import java.util.Collection; import java.util.List; @@ -57,11 +57,40 @@ public interface ConfigurationRepository { @Query("SELECT ticket_category_id_fk, c_value FROM configuration_ticket_category where organization_id_fk = :organizationId and event_id_fk = :eventId and c_key = :key") List<CategoryAndValue> findAllCategoriesAndValueWith(@Bind("organizationId") int organizationId, @Bind("eventId") int eventId, @Bind("key") String key); - @Getter + @Query("select ticket_category_id_fk from configuration_ticket_category where ticket_category_id_fk in (:categories) and c_key = :flagName and c_value = :flagValue") + List<Integer> getCategoriesWithFlag(@Bind("categories") List<Integer> categoriesIds, + @Bind("flagName") String flagName, + @Bind("flagValue") String flagValue); + + @Query("insert into configuration_event (c_key, c_value, description, event_id_fk, organization_id_fk)" + + " select c_key, c_value, description, :targetEventId, :targetOrgId from configuration_event where event_id_fk = :srcEventId and organization_id_fk = :srcOrgId") + int copyEventConfiguration(@Bind("targetEventId") int targetEventId, + @Bind("targetOrgId") int targetOrgId, + @Bind("srcEventId") int srcEventId, + @Bind("srcOrgId") int srcOrgId); + + @Query("insert into configuration_ticket_category (c_key, c_value, description, event_id_fk, organization_id_fk, ticket_category_id_fk)" + + " select c_key, c_value, description, :targetEventId, :targetOrgId, :targetCategoryId from configuration_ticket_category where event_id_fk = :srcEventId and organization_id_fk = :srcOrgId and ticket_category_id_fk = :srcCategoryId") + int copyCategoryConfiguration(@Bind("targetEventId") int targetEventId, + @Bind("targetOrgId") int targetOrgId, + @Bind("targetCategoryId") int targetCategoryId, + @Bind("srcEventId") int srcEventId, + @Bind("srcOrgId") int srcOrgId, + @Bind("srcCategoryId") int srcCategoryId); + class CategoryAndValue { + final int ticketCategoryId; final String value; + public int getTicketCategoryId() { + return ticketCategoryId; + } + + public String getValue() { + return value; + } + public CategoryAndValue(@Column("ticket_category_id_fk") int ticketCategoryId, @Column("c_value") String value) { this.ticketCategoryId = ticketCategoryId; this.value = value; diff --git a/src/main/java/alfio/repository/user/OrganizationRepository.java b/src/main/java/alfio/repository/user/OrganizationRepository.java index 2997d9b498..ef1a5a7524 100644 --- a/src/main/java/alfio/repository/user/OrganizationRepository.java +++ b/src/main/java/alfio/repository/user/OrganizationRepository.java @@ -71,6 +71,4 @@ int update(@Bind("id") int id, "(select * from organization where 'ROLE_ADMIN' in (select role from ba_user inner join authority on ba_user.username = authority.username where ba_user.username = :username) and id = :orgId)") Optional<Organization> findOrganizationForUser(@Bind("username") String username, @Bind("orgId") int orgId); - @Query("delete from organization where id in(:organizationIds) and id not in (select distinct(org_id) from j_user_organization where org_id in(:organizationIds))") - int deleteOrganizationsIfEmpty(@Bind("organizationIds") List<Integer> organizationIds); } diff --git a/src/main/java/alfio/repository/user/UserRepository.java b/src/main/java/alfio/repository/user/UserRepository.java index 1f5bf51955..2bcf4f5102 100644 --- a/src/main/java/alfio/repository/user/UserRepository.java +++ b/src/main/java/alfio/repository/user/UserRepository.java @@ -106,8 +106,8 @@ int update(@Bind("id") int id, @Bind("username") String username, @Bind("firstNa @Query("delete from ba_user where id = :id") int deleteUser(@Bind("id") int id); - @Query("select id from ba_user where user_type = :type and enabled = true and user_creation_time < :date") - List<Integer> findUsersToDeleteOlderThan(@Bind("date") Date date, @Bind("type") User.Type type); + @Query("select id from ba_user where user_type in (:types) and enabled = true and user_creation_time < :date") + List<Integer> findUsersToDeleteOlderThan(@Bind("date") Date date, @Bind("types") Collection<String> types); @Query("delete from authority where username = (select username from ba_user where id = :id)") int deleteUserFromAuthority(@Bind("id") int id); diff --git a/src/main/java/alfio/repository/user/join/UserOrganizationRepository.java b/src/main/java/alfio/repository/user/join/UserOrganizationRepository.java index e26aedf7a0..ebc11d1e07 100644 --- a/src/main/java/alfio/repository/user/join/UserOrganizationRepository.java +++ b/src/main/java/alfio/repository/user/join/UserOrganizationRepository.java @@ -52,4 +52,6 @@ public interface UserOrganizationRepository { @Query("delete from j_user_organization where user_id = :userId and org_id in (:organizationIds)") int removeOrganizationUserLinks(@Bind("userId") int userId, @Bind("organizationIds") Collection<Integer> organizationIds); + @Query("delete from j_user_organization where org_id = :organizationId") + int cleanupOrganization(@Bind("organizationId") int organizationId); } diff --git a/src/main/java/alfio/util/DefaultExceptionHandler.java b/src/main/java/alfio/util/DefaultExceptionHandler.java index 66c3f10fc8..f57355d165 100644 --- a/src/main/java/alfio/util/DefaultExceptionHandler.java +++ b/src/main/java/alfio/util/DefaultExceptionHandler.java @@ -16,10 +16,14 @@ */ package alfio.util; -import lombok.extern.log4j.Log4j2; -@Log4j2 +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class DefaultExceptionHandler implements Thread.UncaughtExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class); + @Override public void uncaughtException(Thread t, Throwable e) { log.error("uncaught Exception", e); diff --git a/src/main/java/alfio/util/EventUtil.java b/src/main/java/alfio/util/EventUtil.java index efd2728ad4..e9f9c8ab1e 100644 --- a/src/main/java/alfio/util/EventUtil.java +++ b/src/main/java/alfio/util/EventUtil.java @@ -33,9 +33,12 @@ import biweekly.property.Method; import biweekly.property.Organizer; import biweekly.property.Status; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.RegExUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.commons.lang3.StringUtils; +import org.flywaydb.core.api.MigrationVersion; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.web.util.UriComponentsBuilder; @@ -53,13 +56,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static alfio.model.EventCheckInInfo.VERSION_FOR_CODE_CASE_INSENSITIVE; import static alfio.model.TicketFieldConfiguration.Context.ATTENDEE; import static alfio.model.system.ConfigurationKeys.*; import static java.time.temporal.ChronoField.*; -@Log4j2 public final class EventUtil { + private static final Logger log = LoggerFactory.getLogger(EventUtil.class); + private EventUtil() {} private static final DateTimeFormatter JSON_TIME_FORMATTER = new DateTimeFormatterBuilder() @@ -207,7 +212,7 @@ public static String getGoogleCalendarURL(Event event, TicketCategory category, .queryParam("ctz", event.getTimeZone()) .queryParam("text", event.getDisplayName()) .queryParam("location", event.getLocation()) - .queryParam("details", description) + .queryParam("details", StringUtils.abbreviate(description, 1024)) .toUriString(); } @@ -238,7 +243,7 @@ public static Function<Ticket, List<TicketFieldConfigurationDescriptionAndValue> public static Optional<String> findMatchingLink(ZoneId eventZoneId, OnlineConfiguration categoryConfiguration, OnlineConfiguration eventConfiguration) { return firstMatchingCallLink(eventZoneId, categoryConfiguration, eventConfiguration) - .map(JoinLink::getLink); + .map(JoinLink::link); } public static Optional<JoinLink> firstMatchingCallLink(ZoneId eventZoneId, OnlineConfiguration categoryConfiguration, OnlineConfiguration eventConfiguration) { @@ -250,8 +255,8 @@ public static Optional<JoinLink> firstMatchingCallLink(ZoneId eventZoneId, Onlin private static Optional<JoinLink> firstMatchingCallLink(OnlineConfiguration onlineConfiguration, ZoneId zoneId, ZonedDateTime now) { return Optional.ofNullable(onlineConfiguration).stream() .flatMap(configuration -> configuration.getCallLinks().stream()) - .sorted(Comparator.comparing(JoinLink::getValidFrom).reversed()) - .filter(joinLink -> now.isBefore(joinLink.getValidTo().atZone(zoneId)) && now.plusSeconds(1).isAfter(joinLink.getValidFrom().atZone(zoneId))) + .sorted(Comparator.comparing(JoinLink::validFrom).reversed()) + .filter(joinLink -> now.isBefore(joinLink.validTo().atZone(zoneId)) && now.plusSeconds(1).isAfter(joinLink.validFrom().atZone(zoneId))) .findFirst(); } @@ -286,4 +291,9 @@ public static String getLocalizedMessage(Map<String, String> messagesByLang, Str return messagesByLang.values().stream().findFirst().orElseThrow(); } + + public static boolean supportsCaseInsensitiveQRCode(String version) { + return version != null + && MigrationVersion.fromVersion(version).compareTo(MigrationVersion.fromVersion(VERSION_FOR_CODE_CASE_INSENSITIVE)) >= 0; + } } diff --git a/src/main/java/alfio/util/ExportUtils.java b/src/main/java/alfio/util/ExportUtils.java index 457c4fc520..244a0790d5 100644 --- a/src/main/java/alfio/util/ExportUtils.java +++ b/src/main/java/alfio/util/ExportUtils.java @@ -18,13 +18,16 @@ import ch.digitalfondue.basicxlsx.Cell; import ch.digitalfondue.basicxlsx.StreamingWorkbook; +import ch.digitalfondue.basicxlsx.Style; import com.opencsv.CSVWriter; +import org.apache.commons.lang3.StringUtils; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.Arrays; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,16 +37,31 @@ public class ExportUtils { private static final int[] BOM_MARKERS = new int[] {0xEF, 0xBB, 0xBF}; + private ExportUtils() {} + public static void exportExcel(String fileName, String sheetName, String[] header, Stream<String[]> data, HttpServletResponse response) throws IOException { + exportExcel(fileName, response, workbook -> addSheetToWorkbook(sheetName, header, data, workbook, workbook.defineStyle().font().bold(true).build())); + } + + public static void exportExcel(String fileName, + HttpServletResponse response, + Consumer<StreamingWorkbook> workbookConsumer) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName); - try (ServletOutputStream out = response.getOutputStream(); StreamingWorkbook workbook = new StreamingWorkbook(out)) { - var boldFont = workbook.defineStyle().font().bold(true).build(); + workbookConsumer.accept(workbook); + } + } + public static void addSheetToWorkbook(String sheetName, + String[] header, + Stream<String[]> data, + StreamingWorkbook workbook, + Style headerStyle) { + try { var headerRow = StreamingWorkbook.row(Arrays.stream(header) - .map(v -> Cell.cell(v).withStyle(boldFont)) + .map(v -> Cell.cell(v).withStyle(headerStyle)) .collect(Collectors.toList())); var dataStream = data @@ -51,7 +69,21 @@ public static void exportExcel(String fileName, String sheetName, String[] heade .map(StreamingWorkbook::row); workbook.withSheet(sheetName, Stream.concat(Stream.of(headerRow), dataStream)); + + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + // https://owasp.org/www-community/attacks/CSV_Injection + private static String escapeFormulaChar(String s) { + var trimmed = StringUtils.trimToEmpty(s); + // tab and carriage return are removed by the trimming + var res = trimmed; + if (StringUtils.startsWithAny(trimmed, "=", "+", "-", "@")) { + res = "\t" + trimmed; // http://georgemauer.net/2017/10/07/csv-injection.html starting with a tab seems to be enough? } + return res; } public static void exportCsv(String fileName, String[] header, Stream<String[]> data, HttpServletResponse response) throws IOException { @@ -63,7 +95,14 @@ public static void exportCsv(String fileName, String[] header, Stream<String[]> out.write(marker); } writer.writeNext(header); - data.forEachOrdered(writer::writeNext); + data.forEachOrdered(d -> { + var copy = Arrays.copyOf(d, d.length); + for (var i = 0; i < copy.length; i++) { + var res = copy[i]; + copy[i] = escapeFormulaChar(res); + } + writer.writeNext(copy); + }); writer.flush(); out.flush(); } diff --git a/src/main/java/alfio/util/FileUtil.java b/src/main/java/alfio/util/FileUtil.java index fa9be41b90..db7b5f04e1 100644 --- a/src/main/java/alfio/util/FileUtil.java +++ b/src/main/java/alfio/util/FileUtil.java @@ -17,6 +17,7 @@ package alfio.util; import alfio.model.BillingDocument; +import org.springframework.http.MediaType; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -41,7 +42,7 @@ public static boolean sendPdf(byte[] res, HttpServletResponse response, String e public static void sendHeaders(HttpServletResponse response, String eventName, String reservationId, BillingDocument billingDocument) { response.setHeader("Content-Disposition", "attachment; filename=\"" + getBillingDocumentFileName(eventName, reservationId, billingDocument) + "\""); - response.setContentType("application/pdf"); + response.setContentType(MediaType.APPLICATION_PDF_VALUE); } public static String getBillingDocumentFileName(String eventShortName, String reservationId, BillingDocument document) { diff --git a/src/main/java/alfio/util/HttpUtils.java b/src/main/java/alfio/util/HttpUtils.java index 5a033dced4..d8cdb1cdd2 100644 --- a/src/main/java/alfio/util/HttpUtils.java +++ b/src/main/java/alfio/util/HttpUtils.java @@ -35,6 +35,7 @@ private HttpUtils() { public static final String CONTENT_TYPE = "Content-Type"; public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8"; public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded"; public static final String MULTIPART_FORM_DATA = "multipart/form-data"; public static final String AUTHORIZATION = "Authorization"; @@ -132,7 +133,7 @@ public enum TYPE { class PartsIterator implements Iterator<byte[]> { - private Iterator<PartsSpecification> iter; + private final Iterator<PartsSpecification> iter; private InputStream currentFileInput; private boolean done; diff --git a/src/main/java/alfio/util/ImageUtil.java b/src/main/java/alfio/util/ImageUtil.java index 826a57d3e3..80241849af 100644 --- a/src/main/java/alfio/util/ImageUtil.java +++ b/src/main/java/alfio/util/ImageUtil.java @@ -26,7 +26,8 @@ import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import javax.imageio.ImageIO; @@ -39,9 +40,10 @@ import static org.apache.commons.lang3.StringUtils.center; import static org.apache.commons.lang3.StringUtils.truncate; -@Log4j2 public final class ImageUtil { + private static final Logger log = LoggerFactory.getLogger(ImageUtil.class); + private static final Cache<String, File> FONT_CACHE = Caffeine.newBuilder() .removalListener((String key, File value, RemovalCause cause) -> { if(value != null) { diff --git a/src/main/java/alfio/util/ItalianTaxIdValidator.java b/src/main/java/alfio/util/ItalianTaxIdValidator.java index d62fca504d..b153716a15 100644 --- a/src/main/java/alfio/util/ItalianTaxIdValidator.java +++ b/src/main/java/alfio/util/ItalianTaxIdValidator.java @@ -16,8 +16,6 @@ */ package alfio.util; -import lombok.AllArgsConstructor; -import lombok.experimental.UtilityClass; import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -25,8 +23,7 @@ import static java.util.Map.entry; import static org.apache.commons.lang3.StringUtils.*; -@UtilityClass -public class ItalianTaxIdValidator { +public final class ItalianTaxIdValidator { private static final char[] CONTROL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); private static final String VOWELS = "AEIOU"; private static final int COMPANY_TAX_ID_LENGTH = 11; @@ -69,6 +66,9 @@ public class ItalianTaxIdValidator { entry('9', new EvenOddValueContainer(9,21)) ); + private ItalianTaxIdValidator() { + } + public static boolean validateFiscalCode(String fiscalCode) { var code = StringUtils.upperCase(trimToNull(fiscalCode)); int length = length(code); @@ -149,6 +149,9 @@ public static boolean validateVatId(String vatId) { int sumEven = 0; int sumOdd = 0; var chars = nr.toCharArray(); + if (chars.length != COMPANY_TAX_ID_LENGTH) { + return false; + } for (int i=0; i < 10; i++) { int val = Character.getNumericValue(chars[i]); if((i + 1) % 2 == 0) { @@ -165,19 +168,16 @@ public static boolean validateVatId(String vatId) { return controlDigit == Character.getNumericValue(chars[10]); } - @AllArgsConstructor - private static class EvenOddValueContainer { - private final int evenValue; - private final int oddValue; + + private record EvenOddValueContainer(int evenValue, int oddValue) { private int getValue(int index) { return (index + 1) % 2 == 0 ? evenValue : oddValue; } } - @AllArgsConstructor - private static class FiscalCodeParts { - private final List<Character> consonants; - private final List<Character> vowels; + + private record FiscalCodeParts(List<Character> consonants, + List<Character> vowels) { } } diff --git a/src/main/java/alfio/util/LocaleUtil.java b/src/main/java/alfio/util/LocaleUtil.java index d59b5e8afb..3b55c9e26d 100644 --- a/src/main/java/alfio/util/LocaleUtil.java +++ b/src/main/java/alfio/util/LocaleUtil.java @@ -21,11 +21,13 @@ import alfio.model.Ticket; import org.apache.commons.lang3.StringUtils; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -66,6 +68,13 @@ public static ZonedDateTime atZone(ZonedDateTime in, ZoneId zone) { return null; } + public static ZonedDateTime atZone(LocalDateTime in, ZoneId zone) { + if(in != null) { + return in.atZone(Objects.requireNonNull(zone)); + } + return null; + } + public static Map<String, String> formatDate(ZonedDateTime date, Map<Locale, String> datePatterns) { if(date == null) { return null; diff --git a/src/main/java/alfio/util/MiscUtils.java b/src/main/java/alfio/util/MiscUtils.java new file mode 100644 index 0000000000..6647447187 --- /dev/null +++ b/src/main/java/alfio/util/MiscUtils.java @@ -0,0 +1,32 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.util; + +import java.util.List; + +public class MiscUtils { + private MiscUtils() { + } + + public static <T> T getAtIndexOrNull(List<T> elements, int index) { + if (elements == null || index < 0 || index >= elements.size()) { + return null; + } + return elements.get(index); + } + +} diff --git a/src/main/java/alfio/util/MustacheCustomTag.java b/src/main/java/alfio/util/MustacheCustomTag.java index 063484030d..b83e89e55e 100644 --- a/src/main/java/alfio/util/MustacheCustomTag.java +++ b/src/main/java/alfio/util/MustacheCustomTag.java @@ -17,8 +17,8 @@ package alfio.util; import alfio.controller.api.support.TicketHelper; +import alfio.model.subscription.SubscriptionDescriptor; import com.samskivert.mustache.Mustache; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.text.StringEscapeUtils; @@ -26,10 +26,14 @@ import org.commonmark.ext.gfm.tables.TablesExtension; import org.commonmark.node.Link; import org.commonmark.node.Node; +import org.commonmark.node.Text; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.text.TextContentRenderer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; import org.springframework.security.web.util.UrlUtils; import java.time.ZonedDateTime; @@ -57,9 +61,12 @@ * <li>optional: locale:YOUR_LOCALE you can define the locale</li> * </ul> */ -@Log4j2 public class MustacheCustomTag { + private static final Logger log = LoggerFactory.getLogger(MustacheCustomTag.class); + + public static final String SUBSCRIPTION_DESCRIPTOR_ATTRIBUTE = "subscriptionDescriptor"; + private MustacheCustomTag() {} private static final Pattern ARG_PATTERN = Pattern.compile("\\[(.*?)]"); @@ -76,6 +83,25 @@ private MustacheCustomTag() {} } }; + /** + * {{#render-markdown}}[markdown][.html|.text]{{/render-markdown}} + * The string must end with either .html or .text, otherwise Markdown won't be parsed + * e.g. + * {{#render-markdown}}(link)[description].html{{/render-markdown}} will produce HTML output + * {{#render-markdown}}(link)[description].text{{/render-markdown}} will produce text/plain output + */ + static final Mustache.Lambda RENDER_MARKDOWN = (frag, out) -> { + String execution = frag.execute().strip(); + if(execution.endsWith(".html")) { + // Markdown renderer will take care of escaping all dangerous content + out.write(renderToHtmlCommonmark(StringUtils.removeEnd(execution, ".html"), null)); + } else if(execution.endsWith(".text")) { + out.write(renderToTextCommonmark(StringUtils.removeEnd(execution, ".text"))); + } else { + out.write(execution); + } + }; + static final Mustache.Lambda COUNTRY_NAME = (frag, out) -> { String execution = frag.execute().trim(); String code = substring(execution, 0, 2); @@ -97,11 +123,10 @@ static String translateCountryCode(String code, Locale locale) { * prefix is optional, unless a suffix is needed. */ static final Function<Object, Mustache.Lambda> ADDITIONAL_FIELD_VALUE = obj -> (frag, out) -> { - if( !(obj instanceof Map) || ((Map<?,?>)obj).isEmpty()) { + if( !(obj instanceof Map<?, ?> fieldNamesAndValues) || ((Map<?,?>)obj).isEmpty()) { log.warn("map not found or empty. Skipping additionalFieldValue tag"); return; } - Map<?, ?> fieldNamesAndValues = (Map<?, ?>) obj; String execution = frag.execute().trim(); Matcher matcher = ARG_PATTERN.matcher(execution); List<String> args = new ArrayList<>(); @@ -120,6 +145,40 @@ static String translateCountryCode(String code, Locale locale) { } }; + static Mustache.Lambda subscriptionDescriptionGenerator(MessageSource messageSource, Map<String, Object> model, Locale locale) { + return (frag, out) -> { + var subscriptionDescriptor = (SubscriptionDescriptor) Objects.requireNonNull(model.get(SUBSCRIPTION_DESCRIPTOR_ATTRIBUTE)); + var usageType = messageSource.getMessage("subscription.usage-type." + subscriptionDescriptor.getUsageType(), null, locale); + switch (subscriptionDescriptor.getValidityType()) { + case STANDARD: + var standardParams = new Object[] { + subscriptionDescriptor.getValidityUnits(), + messageSource.getMessage("subscription.time-unit." + subscriptionDescriptor.getValidityTimeUnit(), null, locale), + usageType + }; + out.write(messageSource.getMessage("subscription.detail.validity.STANDARD.description", standardParams, locale)); + break; + case NOT_SET: + var notSetParams = new Object[] { + subscriptionDescriptor.getMaxEntries(), + messageSource.getMessage("subscription.usage-type." + subscriptionDescriptor.getUsageType(), null, locale), + usageType + }; + out.write(messageSource.getMessage("subscription.detail.validity.NOT_SET.description", notSetParams, locale)); + break; + case CUSTOM: + var formatter = DateTimeFormatter.ofPattern(messageSource.getMessage("common.event.date-format", null, locale)); + out.write(messageSource.getMessage("subscription.detail.validity.CUSTOM.from", null, locale)); + out.write(" " + formatter.format(subscriptionDescriptor.getValidityFrom())); + out.write(messageSource.getMessage("subscription.detail.validity.CUSTOM.to", null, locale)); + out.write(" " + formatter.format(subscriptionDescriptor.getValidityTo())); + out.write(" - " + usageType); + break; + } + }; + } + + private static Pair<String, Optional<Locale>> parseParams(String r) { int indexLocale = r.indexOf(LOCALE_LABEL); @@ -138,27 +197,59 @@ private static Pair<String, Optional<Locale>> parseParams(String r) { private static final List<Extension> COMMONMARK_EXTENSIONS = List.of(TablesExtension.create()); private static final Parser COMMONMARK_PARSER = Parser.builder().extensions(COMMONMARK_EXTENSIONS).build(); - private static final HtmlRenderer COMMONMARK_RENDERER = HtmlRenderer.builder().extensions(COMMONMARK_EXTENSIONS).attributeProviderFactory((ctx) -> new TargetBlankProvider()).build(); + private static final HtmlRenderer COMMONMARK_RENDERER = HtmlRenderer.builder().extensions(COMMONMARK_EXTENSIONS).attributeProviderFactory(ctx -> new TargetBlankProvider()).build(); private static final TextContentRenderer COMMONMARK_TEXT_RENDERER = TextContentRenderer.builder().extensions(COMMONMARK_EXTENSIONS).build(); + private static final ThreadLocal<String> A11Y_NEW_TAB_LABEL = new ThreadLocal<>(); //Open in a new window if the link contains an absolute url private static class TargetBlankProvider implements AttributeProvider { @Override public void setAttributes(Node node, String tagName, Map<String, String> attributes) { - if (node instanceof Link) { - Link l = (Link) node; + if (node instanceof Link l) { String destination = StringUtils.trimToEmpty(l.getDestination()); + var scheme = getScheme(destination); + scheme.ifPresent(resolvedScheme -> { + if (!Set.of("http", "https").contains(resolvedScheme)) { + log.info("User tried to set an url with scheme {}, only http/https are accepted, href has been removed", resolvedScheme); + attributes.remove("href"); + } + }); if (UrlUtils.isAbsoluteUrl(destination)) { + // accept only http or https protocols if we have an absolute link, else we override with an empty string attributes.put("target", "_blank"); attributes.put("rel", "nofollow noopener noreferrer"); + var newTabLabel = A11Y_NEW_TAB_LABEL.get(); + if (newTabLabel != null) { + attributes.put("aria-label", ((Text)node.getFirstChild()).getLiteral() + " " + newTabLabel); + } } } } } - public static String renderToHtmlCommonmarkEscaped(String input) { - Node document = COMMONMARK_PARSER.parse(StringEscapeUtils.escapeHtml4(input)); - return COMMONMARK_RENDERER.render(document); + return renderToHtmlCommonmarkEscaped(input, null); + } + + /** + * return lowercase scheme if present + */ + private static Optional<String> getScheme(String uri) { + var s = StringUtils.trimToEmpty(uri).toLowerCase(Locale.ROOT); + return s.indexOf(':') >= 0 ? Optional.of(StringUtils.substringBefore(s, ':')) : Optional.empty(); + } + + public static String renderToHtmlCommonmarkEscaped(String input, String localizedNewWindowLabel) { + return renderToHtmlCommonmark(StringEscapeUtils.escapeHtml4(input), localizedNewWindowLabel); + } + + private static String renderToHtmlCommonmark(String input, String localizedNewWindowLabel) { + try { + A11Y_NEW_TAB_LABEL.set(localizedNewWindowLabel); + Node document = COMMONMARK_PARSER.parse(input); + return COMMONMARK_RENDERER.render(document); + } finally { + A11Y_NEW_TAB_LABEL.remove(); + } } public static String renderToTextCommonmark(String input) { diff --git a/src/main/java/alfio/util/ObjectDiffUtil.java b/src/main/java/alfio/util/ObjectDiffUtil.java index d31d07a61f..a214ea5d89 100644 --- a/src/main/java/alfio/util/ObjectDiffUtil.java +++ b/src/main/java/alfio/util/ObjectDiffUtil.java @@ -17,8 +17,6 @@ package alfio.util; import alfio.model.Ticket; -import lombok.AllArgsConstructor; -import lombok.Getter; import org.apache.commons.lang3.builder.CompareToBuilder; import org.springframework.beans.BeanUtils; import org.springframework.util.ReflectionUtils; @@ -83,14 +81,35 @@ public static <T> List<Change> diff(T before, T after, Class<T> objectType) { return diffUntyped(beforeAsMap, afterAsMap, "/", ""); } - @AllArgsConstructor - @Getter public static class Change implements Comparable<Change> { private final String propertyName; private final State state; private final Object oldValue; private final Object newValue; + public String getPropertyName() { + return propertyName; + } + + public State getState() { + return state; + } + + public Object getOldValue() { + return oldValue; + } + + public Object getNewValue() { + return newValue; + } + + public Change(String propertyName, State state, Object oldValue, Object newValue) { + this.propertyName = propertyName; + this.state = state; + this.oldValue = oldValue; + this.newValue = newValue; + } + @Override public int compareTo(Change change) { return new CompareToBuilder() diff --git a/src/main/java/alfio/util/PinGenerator.java b/src/main/java/alfio/util/PinGenerator.java index a7075d177c..da3d5c6866 100644 --- a/src/main/java/alfio/util/PinGenerator.java +++ b/src/main/java/alfio/util/PinGenerator.java @@ -16,7 +16,6 @@ */ package alfio.util; -import lombok.experimental.UtilityClass; import org.apache.commons.lang3.StringUtils; import org.springframework.util.Assert; @@ -24,13 +23,14 @@ import java.util.Objects; import java.util.regex.Pattern; -@UtilityClass -public class PinGenerator { +public final class PinGenerator { private static final String ALLOWED_CHARS = "ACDEFGHJKLMNPQRTUVWXY34679"; private static final Pattern VALIDATION_PATTERN = Pattern.compile("^["+ALLOWED_CHARS+"]+$"); private static final int PIN_LENGTH = 6; + private PinGenerator() { + } public static String uuidToPin(String uuid, int pinLength) { var src = new BigInteger(uuid.replace("-", "").substring(0, pinLength+1), 16); diff --git a/src/main/java/alfio/util/RenderedTemplate.java b/src/main/java/alfio/util/RenderedTemplate.java index 35baa75fe8..ed5da7bc91 100644 --- a/src/main/java/alfio/util/RenderedTemplate.java +++ b/src/main/java/alfio/util/RenderedTemplate.java @@ -16,16 +16,25 @@ */ package alfio.util; -import lombok.Getter; - import java.util.Map; -@Getter public class RenderedTemplate { private final String textPart; private final String htmlPart; private final Map<String, Object> srcModel; + public Map<String, Object> getSrcModel() { + return srcModel; + } + + public String getHtmlPart() { + return htmlPart; + } + + public String getTextPart() { + return textPart; + } + private RenderedTemplate(String textPart, String htmlPart, Map<String, Object> model) { this.textPart = textPart; this.htmlPart = htmlPart; diff --git a/src/main/java/alfio/util/RequestUtils.java b/src/main/java/alfio/util/RequestUtils.java index 446de50bd4..936b7ee3b1 100644 --- a/src/main/java/alfio/util/RequestUtils.java +++ b/src/main/java/alfio/util/RequestUtils.java @@ -18,9 +18,9 @@ import alfio.model.ContentLanguage; import alfio.model.Event; -import lombok.experimental.UtilityClass; -import lombok.extern.log4j.Log4j2; import org.apache.commons.collections4.IteratorUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.web.context.request.ServletWebRequest; @@ -29,18 +29,18 @@ import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import java.security.Principal; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Optional; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; -@Log4j2 -@UtilityClass -public class RequestUtils { +public final class RequestUtils { + + private RequestUtils() { + } + + private static final Logger log = LoggerFactory.getLogger(RequestUtils.class); public static Optional<String> readRequest(HttpServletRequest request) { try (ServletInputStream is = request.getInputStream()){ @@ -58,6 +58,14 @@ public static boolean isSocialMediaShareUA(String ua) { } + public static Locale getMatchingLocale(ServletWebRequest request, List<String> allowedLanguages) { + var l = requireNonNull(request.getNativeRequest(HttpServletRequest.class)).getLocales(); + List<Locale> locales = l != null ? IteratorUtils.toList(l.asIterator()) : Collections.emptyList(); + var selectedLocale = locales.stream().map(Locale::getLanguage).filter(allowedLanguages::contains).findFirst() + .orElseGet(() -> allowedLanguages.stream().findFirst().orElseThrow()); + return LocaleUtil.forLanguageTag(selectedLocale); + } + /** * From a given request, return the best locale for the user * @@ -66,19 +74,26 @@ public static boolean isSocialMediaShareUA(String ua) { * @return */ public static Locale getMatchingLocale(ServletWebRequest request, Event event) { - var allowedLanguages = event.getContentLanguages().stream().map(ContentLanguage::getLanguage).collect(Collectors.toSet()); - var l = requireNonNull(request.getNativeRequest(HttpServletRequest.class)).getLocales(); - List<Locale> locales = l != null ? IteratorUtils.toList(l.asIterator()) : Collections.emptyList(); - var selectedLocale = locales.stream().map(Locale::getLanguage).filter(allowedLanguages::contains).findFirst() - .orElseGet(() -> event.getContentLanguages().stream().findFirst().orElseThrow().getLanguage()); - return LocaleUtil.forLanguageTag(selectedLocale); + var allowedLanguages = event.getContentLanguages().stream().map(ContentLanguage::getLanguage).collect(Collectors.toList()); + return getMatchingLocale(request, allowedLanguages); } public static boolean isAdmin(Principal principal) { if (principal instanceof Authentication) { - return ((Authentication) principal).getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .anyMatch("ROLE_ADMIN"::equals); + return hasRole((Authentication) principal, "ROLE_ADMIN"); + } + return false; + } + + private static boolean hasRole(Authentication principal, String role) { + return principal.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .anyMatch(role::equals); + } + + public static boolean isSystemApiKey(Principal principal) { + if (principal instanceof Authentication) { + return hasRole((Authentication)principal, "ROLE_SYSTEM_API_CLIENT"); } return false; } diff --git a/src/main/java/alfio/util/ReservationUtil.java b/src/main/java/alfio/util/ReservationUtil.java index 5337971011..7a2c9a955c 100644 --- a/src/main/java/alfio/util/ReservationUtil.java +++ b/src/main/java/alfio/util/ReservationUtil.java @@ -22,11 +22,13 @@ import alfio.manager.PromoCodeRequestManager; import alfio.manager.TicketReservationManager; import alfio.manager.support.response.ValidatedResponse; +import alfio.manager.system.ConfigurationManager; import alfio.model.*; import alfio.model.modification.ASReservationWithOptionalCodeModification; import alfio.model.modification.AdditionalServiceReservationModification; -import alfio.model.modification.TicketReservationModification; +import alfio.model.modification.ReservationRequest; import alfio.model.modification.TicketReservationWithOptionalCodeModification; +import alfio.repository.TicketCategoryRepository; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.validation.BindingResult; @@ -34,21 +36,17 @@ import java.math.BigDecimal; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; +import java.util.*; import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; -import static java.util.stream.Collectors.toList; public class ReservationUtil { private ReservationUtil() { } - public static Optional<String> checkPromoCode(ReservationCreate createRequest, + public static <T extends ReservationRequest> Optional<String> checkPromoCode(ReservationCreate<T> createRequest, Event event, PromoCodeRequestManager promoCodeRequestManager, BindingResult bindingResult) { @@ -68,7 +66,7 @@ public static Optional<String> checkPromoCode(ReservationCreate createRequest, } - public static Optional<Pair<List<TicketReservationWithOptionalCodeModification>, List<ASReservationWithOptionalCodeModification>>> validateCreateRequest(ReservationCreate request, + public static Optional<Pair<List<TicketReservationWithOptionalCodeModification>, List<ASReservationWithOptionalCodeModification>>> validateCreateRequest(ReservationCreate<? extends ReservationRequest> request, Errors bindingResult, TicketReservationManager tickReservationManager, EventManager eventManager, @@ -84,10 +82,9 @@ public static Optional<Pair<List<TicketReservationWithOptionalCodeModification>, return Optional.empty(); } - List<Pair<TicketReservationModification, Integer>> maxTicketsByTicketReservation = selected(request.getTickets()).stream() - .map(r -> Pair.of(r, tickReservationManager.maxAmountOfTicketsForCategory(event, r.getTicketCategoryId(), validatedPromoCodeDiscount))) - .collect(toList()); - Optional<Pair<TicketReservationModification, Integer>> error = maxTicketsByTicketReservation.stream() + List<Pair<ReservationRequest, Integer>> maxTicketsByTicketReservation = selected(request.getTickets()).stream() + .map(r -> Pair.of((ReservationRequest) r, tickReservationManager.maxAmountOfTicketsForCategory(event, r.getTicketCategoryId(), validatedPromoCodeDiscount))).toList(); + Optional<Pair<ReservationRequest, Integer>> error = maxTicketsByTicketReservation.stream() .filter(p -> p.getKey().getQuantity() > p.getValue()) .findAny(); @@ -96,7 +93,7 @@ public static Optional<Pair<List<TicketReservationWithOptionalCodeModification>, return Optional.empty(); } - final List<TicketReservationModification> categories = selected(request.getTickets()); + final var categories = selected(request.getTickets()); final List<AdditionalServiceReservationModification> additionalServices = selectedAdditionalServices(request.getAdditionalServices()); final boolean validCategorySelection = categories.stream().allMatch(c -> { @@ -127,16 +124,16 @@ public static Optional<Pair<List<TicketReservationWithOptionalCodeModification>, // final ZonedDateTime now = event.now(ClockProvider.clock()); maxTicketsByTicketReservation.forEach(pair -> validateCategory(bindingResult, tickReservationManager, eventManager, event, pair.getRight(), res, specialCode, now, pair.getLeft())); - return bindingResult.hasErrors() ? Optional.empty() : Optional.of(Pair.of(res, additionalServices.stream().map(as -> new ASReservationWithOptionalCodeModification(as, specialCode)).collect(Collectors.toList()))); + return bindingResult.hasErrors() ? Optional.empty() : Optional.of(Pair.of(res, additionalServices.stream().map(as -> new ASReservationWithOptionalCodeModification(as, specialCode)).toList())); } - private static int ticketSelectionCount(List<TicketReservationModification> tickets) { - return selected(tickets).stream().mapToInt(TicketReservationModification::getQuantity).sum(); + private static <T extends ReservationRequest> int ticketSelectionCount(List<T> tickets) { + return selected(tickets).stream().mapToInt(ReservationRequest::getQuantity).sum(); } - private static void validateCategory(Errors bindingResult, TicketReservationManager tickReservationManager, EventManager eventManager, + private static <T extends ReservationRequest> void validateCategory(Errors bindingResult, TicketReservationManager tickReservationManager, EventManager eventManager, Event event, int maxAmountOfTicket, List<TicketReservationWithOptionalCodeModification> res, - Optional<SpecialPrice> specialCode, ZonedDateTime now, TicketReservationModification r) { + Optional<SpecialPrice> specialCode, ZonedDateTime now, T r) { TicketCategory tc = eventManager.getTicketCategoryById(r.getTicketCategoryId(), event.getId()); SaleableTicketCategory ticketCategory = new SaleableTicketCategory(tc, now, event, tickReservationManager.countAvailableTickets(event, tc), maxAmountOfTicket, null); @@ -148,12 +145,12 @@ private static void validateCategory(Errors bindingResult, TicketReservationMana res.add(new TicketReservationWithOptionalCodeModification(r, ticketCategory.isAccessRestricted() ? specialCode : Optional.empty())); } - private static List<TicketReservationModification> selected(List<TicketReservationModification> reservation) { + private static <T extends ReservationRequest> List<T> selected(List<T> reservation) { return ofNullable(reservation) .orElse(emptyList()) .stream() .filter(e -> e != null && e.getQuantity() != null && e.getTicketCategoryId() != null && e.getQuantity() > 0) - .collect(toList()); + .toList(); } private static List<AdditionalServiceReservationModification> selectedAdditionalServices(List<AdditionalServiceReservationModification> additionalServices) { @@ -161,6 +158,51 @@ private static List<AdditionalServiceReservationModification> selectedAdditional .orElse(emptyList()) .stream() .filter(e -> e != null && e.getQuantity() != null && e.getAdditionalServiceId() != null && e.getQuantity() > 0) - .collect(toList()); + .toList(); + } + + public static boolean hasPrivacyPolicy(PurchaseContext event) { + return StringUtils.isNotBlank(event.getPrivacyPolicyLinkOrNull()); + } + + public static String ticketUpdateUrl(Event event, Ticket ticket, ConfigurationManager configurationManager) { + return configurationManager.baseUrl(event) + "/event/" + event.getShortName() + "/ticket/" + ticket.getUuid() + "/update?lang=" + ticket.getUserLanguage(); + } + + public static String reservationUrl(String baseUrl, String reservationId, + PurchaseContext purchaseContext, + String userLanguage, + String additionalParams) { + var cleanParams = StringUtils.trimToNull(additionalParams); + return StringUtils.removeEnd(baseUrl, "/") + + "/" + purchaseContext.getType() + + "/" + purchaseContext.getPublicIdentifier() + + "/reservation/" + reservationId + + "?lang="+userLanguage + + (cleanParams != null ? "&" + cleanParams : ""); + } + public static String reservationUrl(String baseUrl, String reservationId, PurchaseContext purchaseContext, String userLanguage) { + return reservationUrl(baseUrl, reservationId, purchaseContext, userLanguage, null); + } + + public static String reservationUrl(TicketReservation reservation, PurchaseContext purchaseContext, ConfigurationManager configurationManager) { + return reservationUrl(configurationManager.baseUrl(purchaseContext), reservation.getId(), purchaseContext, reservation.getUserLanguage()); + } + + public static List<TicketWithCategory> collectTicketsWithCategory(Map<Integer, List<Ticket>> ticketsByCategory, TicketCategoryRepository ticketCategoryRepository) { + final List<TicketWithCategory> ticketsWithCategory; + if(!ticketsByCategory.isEmpty()) { + ticketsWithCategory = ticketCategoryRepository.findByIds(ticketsByCategory.keySet()) + .stream() + .flatMap(tc -> ticketsByCategory.get(tc.getId()).stream().map(t -> new TicketWithCategory(t, tc))) + .toList(); + } else { + ticketsWithCategory = Collections.emptyList(); + } + return ticketsWithCategory; + } + + public static Locale getReservationLocale(TicketReservation reservation) { + return StringUtils.isEmpty(reservation.getUserLanguage()) ? Locale.ENGLISH : LocaleUtil.forLanguageTag(reservation.getUserLanguage()); } } diff --git a/src/main/java/alfio/util/SqlUtils.java b/src/main/java/alfio/util/SqlUtils.java index c049e38c8c..de58032b3e 100644 --- a/src/main/java/alfio/util/SqlUtils.java +++ b/src/main/java/alfio/util/SqlUtils.java @@ -16,7 +16,6 @@ */ package alfio.util; -import lombok.experimental.UtilityClass; import org.postgresql.util.PSQLException; import org.postgresql.util.ServerErrorMessage; import org.springframework.jdbc.UncategorizedSQLException; @@ -25,8 +24,12 @@ import java.time.ZonedDateTime; import java.util.Optional; -@UtilityClass -public class SqlUtils { + +public final class SqlUtils { + + private SqlUtils() { + } + public static Optional<ServerErrorMessage> findServerError(UncategorizedSQLException exception) { for (var throwable : exception.getSQLException()) { if(throwable instanceof PSQLException && ((PSQLException)throwable).getServerErrorMessage() != null) { diff --git a/src/main/java/alfio/util/TemplateManager.java b/src/main/java/alfio/util/TemplateManager.java index 58341ebaf3..a5a1720d93 100644 --- a/src/main/java/alfio/util/TemplateManager.java +++ b/src/main/java/alfio/util/TemplateManager.java @@ -18,6 +18,7 @@ import alfio.manager.UploadedResourceManager; import alfio.manager.i18n.MessageSourceManager; +import alfio.manager.system.ConfigurationLevel; import alfio.manager.system.ConfigurationManager; import alfio.model.Event; import alfio.model.PurchaseContext; @@ -25,8 +26,9 @@ import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache.Compiler; import com.samskivert.mustache.Template; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; @@ -44,19 +46,20 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static alfio.util.MustacheCustomTag.ADDITIONAL_FIELD_VALUE; -import static alfio.util.MustacheCustomTag.COUNTRY_NAME; +import static alfio.util.MustacheCustomTag.*; /** * For hiding the ugliness :) * */ -@Log4j2 public class TemplateManager { + private static final Logger log = LoggerFactory.getLogger(TemplateManager.class); + public static final String METADATA_ATTRIBUTES_KEY = "metadata-attributes"; public static final String ADDITIONAL_FIELDS_KEY = "additional-fields"; public static final String VAT_TRANSLATION_TEMPLATE_KEY = "vatTranslation"; + public static final String MAIL_FOOTER = "mailFooter"; private final MessageSourceManager messageSourceManager; @@ -103,16 +106,22 @@ private static String dateFormatter(Object o) { private RenderedTemplate renderMultipartTemplate(PurchaseContext purchaseContext, TemplateResource templateResource, Map<String, Object> model, Locale locale) { var enrichedModel = modelEnricher(model, purchaseContext, locale); + var options = configurationManager.getFor(EnumSet.of(ConfigurationKeys.MAIL_FOOTER, ConfigurationKeys.ENABLE_HTML_EMAILS), purchaseContext.getConfigurationLevel()); + var mailFooter = options.get(ConfigurationKeys.MAIL_FOOTER); + enrichedModel.put("hasMailFooter", mailFooter.isPresent()); + enrichedModel.put(MAIL_FOOTER, mailFooter.getValueOrNull()); var isMultipart = templateResource.isMultipart(); var textRender = render(new ClassPathResource(templateResource.classPath()), enrichedModel, locale, purchaseContext, isMultipart ? TemplateOutput.TEXT : templateResource.getTemplateOutput()); - boolean htmlEnabled = configurationManager.getFor(ConfigurationKeys.ENABLE_HTML_EMAILS, purchaseContext.getConfigurationLevel()).getValueAsBooleanOrDefault(); - - var htmlRender = isMultipart && htmlEnabled ? - render(new ClassPathResource(templateResource.htmlClassPath()), enrichedModel, locale, purchaseContext, TemplateOutput.HTML) : - null; - + boolean htmlEnabled = options.get(ConfigurationKeys.ENABLE_HTML_EMAILS).getValueAsBooleanOrDefault(); + + String htmlRender = null; + + if(isMultipart && htmlEnabled) { + htmlRender = render(new ClassPathResource(templateResource.htmlClassPath()), enrichedModel, locale, purchaseContext, TemplateOutput.HTML); + } + return RenderedTemplate.multipart(textRender, htmlRender, model); } @@ -147,17 +156,29 @@ private Map<String, Object> modelEnricher(Map<String, Object> model, PurchaseCon private String render(Resource resource, Map<String, Object> model, Locale locale, PurchaseContext purchaseContext, TemplateOutput templateOutput) { try { + var messageSource = messageSourceManager.getMessageSourceFor(purchaseContext); + var configuration = configurationManager.getFor(EnumSet.of(ConfigurationKeys.USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL, ConfigurationKeys.ENABLE_WALLET, ConfigurationKeys.ENABLE_PASS), ConfigurationLevel.purchaseContext(purchaseContext)); + boolean usePartnerCode = Objects.requireNonNull(configuration.get(ConfigurationKeys.USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL)) + .getValueAsBooleanOrDefault(); ModelAndView mv = new ModelAndView(); mv.getModelMap().addAllAttributes(model); mv.addObject("format-date", MustacheCustomTag.FORMAT_DATE); mv.addObject("country-name", COUNTRY_NAME); + mv.addObject("render-markdown", RENDER_MARKDOWN); mv.addObject("additional-field-value", ADDITIONAL_FIELD_VALUE.apply(model.get(ADDITIONAL_FIELDS_KEY))); mv.addObject("metadata-value", ADDITIONAL_FIELD_VALUE.apply(model.get(METADATA_ATTRIBUTES_KEY))); - mv.addObject("i18n", new CustomLocalizationMessageInterceptor(locale, messageSourceManager.getMessageSourceFor(purchaseContext)).createTranslator()); + mv.addObject("i18n", new CustomLocalizationMessageInterceptor(locale, messageSource).createTranslator()); + mv.addObject("discountCodeDescription", messageSource.getMessage("show-event.promo-code-type." + (usePartnerCode ? "partner" : "promotional"), null, locale)); + mv.addObject("subscriptionDescription", MustacheCustomTag.subscriptionDescriptionGenerator(messageSource, model, locale)); var updatedModel = mv.getModel(); updatedModel.putIfAbsent("custom-header-text", ""); updatedModel.putIfAbsent("custom-body-text", ""); updatedModel.putIfAbsent("custom-footer-text", ""); + boolean googleWalletEnabled = configuration.get(ConfigurationKeys.ENABLE_WALLET).getValueAsBooleanOrDefault(); + boolean appleWalletEnabled = configuration.get(ConfigurationKeys.ENABLE_PASS).getValueAsBooleanOrDefault(); + updatedModel.putIfAbsent("googleWalletEnabled", googleWalletEnabled); + updatedModel.putIfAbsent("appleWalletEnabled", appleWalletEnabled); + updatedModel.putIfAbsent("walletEnabled", googleWalletEnabled || appleWalletEnabled); return compile(resource, templateOutput).execute(mv.getModel()); } catch (Exception e) { log.error("TemplateManager: got exception while generating a template", e); diff --git a/src/main/java/alfio/util/TemplateResource.java b/src/main/java/alfio/util/TemplateResource.java index 4630b7c95b..af9ec810d5 100644 --- a/src/main/java/alfio/util/TemplateResource.java +++ b/src/main/java/alfio/util/TemplateResource.java @@ -17,16 +17,20 @@ package alfio.util; import alfio.model.*; +import alfio.model.PurchaseContext.PurchaseContextType; +import alfio.model.api.v1.admin.subscription.SubscriptionConfiguration; +import alfio.model.metadata.SubscriptionMetadata; import alfio.model.modification.SendCodeModification; +import alfio.model.subscription.Subscription; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.subscription.SubscriptionDescriptor.SubscriptionValidityType; import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; import alfio.model.user.Organization; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Delegate; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.http.MediaType; import java.math.BigDecimal; import java.time.ZoneId; @@ -36,6 +40,7 @@ import java.util.stream.Collectors; import static alfio.util.ImageUtil.createQRCode; +import static alfio.util.MustacheCustomTag.SUBSCRIPTION_DESCRIPTOR_ATTRIBUTE; import static alfio.util.TemplateManager.ADDITIONAL_FIELDS_KEY; import static alfio.util.TemplateManager.METADATA_ATTRIBUTES_KEY; import static org.springframework.http.MediaType.APPLICATION_PDF_VALUE; @@ -49,61 +54,61 @@ public enum TemplateResource { CONFIRMATION_EMAIL_FOR_ORGANIZER("/alfio/templates/confirmation-email-for-organizer", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.HTML) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, SEND_RESERVED_CODE("/alfio/templates/send-reserved-code-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareModelForSendReservedCode(organization, event, new SendCodeModification("CODE", "Firstname Lastname", "email@email.tld", "en"), "http://your-domain.tld/event-page", "Promotional"); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareModelForSendReservedCode(organization, (Event) event, new SendCodeModification("CODE", "Firstname Lastname", "email@email.tld", "en"), "http://your-domain.tld/event-page", "Promotional"); } }, CONFIRMATION_EMAIL("/alfio/templates/confirmation-email", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, CONFIRMATION_EMAIL_SUBSCRIPTION("/alfio/templates/confirmation-email-subscription", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, OFFLINE_RESERVATION_EXPIRED_EMAIL("/alfio/templates/offline-reservation-expired-email-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, CHARGE_ATTEMPT_FAILED_EMAIL("/alfio/templates/charge-failed-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForChargeFailed(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForChargeFailed(organization, (Event) event); } }, CHARGE_ATTEMPT_FAILED_EMAIL_FOR_ORGANIZER("/alfio/templates/charge-failed-organizer-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForChargeFailed(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForChargeFailed(organization, (Event) event); } }, CREDIT_NOTE_ISSUED_EMAIL("/alfio/templates/credit-note-issued-email-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER("/alfio/templates/offline-reservation-expiring-email-for-organizer", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleModelForOfflineReservationExpiringEmailForOrganizer(event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleModelForOfflineReservationExpiringEmailForOrganizer((Event) event); } }, OFFLINE_PAYMENT_MATCHES_FOUND("/alfio/templates/offline-payment-matches-found-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { return Map.of( "matchingCount", 3, "eventName", event.getDisplayName(), @@ -118,27 +123,28 @@ public Map<String, Object> prepareSampleModel(Organization organization, Event e }, REMINDER_EMAIL("/alfio/templates/reminder-email-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, REMINDER_TICKET_ADDITIONAL_INFO("/alfio/templates/reminder-ticket-additional-info.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareModelForReminderTicketAdditionalInfo(organization, event, sampleTicket(event.getZoneId()), "http://your-domain.tld/ticket-url"); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareModelForReminderTicketAdditionalInfo(organization, (Event) event, sampleTicket(event.getZoneId()), "http://your-domain.tld/ticket-url"); } }, REMINDER_TICKETS_ASSIGNMENT_EMAIL("/alfio/templates/reminder-tickets-assignment-email-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return prepareSampleDataForConfirmationEmail(organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return prepareSampleDataForConfirmationEmail(organization, (Event) event); } }, TICKET_EMAIL("/alfio/templates/ticket-email", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional<ImageData> imageData) { + Event event = (Event) purchaseContext; var now = event.now(ClockProvider.clock()); TicketCategory ticketCategory = new TicketCategory(0, now, now, 42, "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null, null, null, "CHF", 0, null, TicketCategory.TicketAccessType.INHERIT); return buildModelForTicketEmail(organization, event, sampleTicketReservation(event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(event.getZoneId()), ticketCategory, Map.of()); @@ -147,7 +153,8 @@ public Map<String, Object> prepareSampleModel(Organization organization, Event e TICKET_EMAIL_FOR_ONLINE_EVENT("/alfio/templates/ticket-email-online", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional<ImageData> imageData) { + Event event = (Event) purchaseContext; var now = event.now(ClockProvider.clock()); TicketCategory ticketCategory = new TicketCategory(0, now, now, 42, "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null, null, null, "CHF", 0, null, TicketCategory.TicketAccessType.INHERIT); return buildModelForTicketEmail(organization, event, sampleTicketReservation(event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(event.getZoneId()), ticketCategory, Map.of("onlineCheckInUrl", "https://your-domain.tld/check-in", "prerequisites", "An internet connection is required to join the event")); @@ -156,29 +163,30 @@ public Map<String, Object> prepareSampleModel(Organization organization, Event e TICKET_HAS_CHANGED_OWNER("/alfio/templates/ticket-has-changed-owner-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return buildModelForTicketHasChangedOwner(organization, event, sampleTicket(event.getZoneId()), sampleTicket("NewFirstname", "NewLastname", "newemail@email.tld", event.getZoneId()), "http://your-domain.tld/ticket-url"); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return buildModelForTicketHasChangedOwner(organization, (Event) event, sampleTicket(event.getZoneId()), sampleTicket("NewFirstname", "NewLastname", "newemail@email.tld", event.getZoneId()), "http://your-domain.tld/ticket-url"); } }, TICKET_HAS_BEEN_CANCELLED("/alfio/templates/ticket-has-been-cancelled-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return buildModelForTicketHasBeenCancelled(organization, event, sampleTicket(event.getZoneId())); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return buildModelForTicketHasBeenCancelled(organization, (Event) event, sampleTicket(event.getZoneId())); } }, TICKET_HAS_BEEN_CANCELLED_ADMIN("/alfio/templates/ticket-has-been-cancelled-admin-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return buildModelForTicketHasBeenCancelledAdmin(organization, event, sampleTicket(event.getZoneId()), "Category", Collections.emptyList(), asi -> Optional.empty()); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return buildModelForTicketHasBeenCancelledAdmin(organization, (Event) event, sampleTicket(event.getZoneId()), "Category", Collections.emptyList(), asi -> Optional.empty()); } }, TICKET_PDF("/alfio/templates/ticket.ms", APPLICATION_PDF_VALUE, TemplateManager.TemplateOutput.HTML) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional<ImageData> imageData) { + Event event = (Event) purchaseContext; var now = event.now(ClockProvider.clock()); TicketCategory ticketCategory = new TicketCategory(0, now, now, 42, "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null, null, null, "CHF", 0, null, TicketCategory.TicketAccessType.INHERIT); var ticketWithMetadata = TicketWithMetadataAttributes.build(sampleTicket(event.getZoneId()), null); @@ -187,40 +195,80 @@ public Map<String, Object> prepareSampleModel(Organization organization, Event e }, RECEIPT_PDF("/alfio/templates/receipt.ms", APPLICATION_PDF_VALUE, TemplateManager.TemplateOutput.HTML) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return sampleBillingDocument(imageData, organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return sampleBillingDocument(imageData, organization, (Event) event); } }, INVOICE_PDF("/alfio/templates/invoice.ms", APPLICATION_PDF_VALUE, TemplateManager.TemplateOutput.HTML) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return sampleBillingDocument(imageData, organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return sampleBillingDocument(imageData, organization, (Event) event); } }, CREDIT_NOTE_PDF("/alfio/templates/credit-note.ms", APPLICATION_PDF_VALUE, TemplateManager.TemplateOutput.HTML) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return sampleBillingDocument(imageData, organization, event); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return sampleBillingDocument(imageData, organization, (Event) event); + } + }, + SUBSCRIPTION_PDF("/alfio/templates/subscription.ms", APPLICATION_PDF_VALUE, TemplateManager.TemplateOutput.HTML, PurchaseContextType.subscription) { + @Override + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional<ImageData> imageData) { + var subscriptionDescriptor = (SubscriptionDescriptor) purchaseContext; + var zoneId = subscriptionDescriptor.getZoneId(); + var subscription = new Subscription(UUID.randomUUID(), + "firstName", + "lastName", + "email@example.org", + subscriptionDescriptor.getId(), + RESERVATION_ID_VALUE, + organization.getId(), + ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), + ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), + 100, + 0, + "CHF", + AllocationStatus.ACQUIRED, + 1, + ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), + ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)).plusDays(1), + ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), + subscriptionDescriptor.getTimeZone() + ); + var subscriptionMetadata = new SubscriptionMetadata(Map.of("key", "value"), SubscriptionConfiguration.defaultConfiguration()); + return buildModelForSubscriptionPDF(subscription, subscriptionDescriptor, organization, subscriptionMetadata, imageData, RESERVATION_ID_VALUE, Locale.ENGLISH, sampleTicketReservation(zoneId)); } }, WAITING_QUEUE_JOINED("/alfio/templates/waiting-queue-joined.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { - return buildModelForWaitingQueueJoined(organization, event, new CustomerName("Firstname Lastname", "Firstname", "Lastname", event.mustUseFirstAndLastName())); + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { + return buildModelForWaitingQueueJoined(organization, (Event) event, new CustomerName("Firstname Lastname", "Firstname", "Lastname", event.mustUseFirstAndLastName())); } }, WAITING_QUEUE_RESERVATION_EMAIL("/alfio/templates/waiting-queue-reservation-email-txt.ms", TEXT_PLAIN_VALUE, TemplateManager.TemplateOutput.TEXT) { @Override - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional<ImageData> imageData) { + Event event = (Event) purchaseContext; var clock = ClockProvider.clock().withZone(event.getZoneId()); WaitingQueueSubscription subscription = new WaitingQueueSubscription(0, ZonedDateTime.now(clock), event.getId(), "ACQUIRED", "Firstname Lastname", "Firstname", "Lastname", - "email@email.tld", "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", "en", null, WaitingQueueSubscription.Type.PRE_SALES); + "email@email.tld", RESERVATION_ID_VALUE, "en", null, WaitingQueueSubscription.Type.PRE_SALES); return buildModelForWaitingQueueReservationEmail(organization, event, subscription, "http://your-domain.tld/reservation-url", ZonedDateTime.now(clock)); } - }; + }, + CUSTOM_MESSAGE("/alfio/templates/custom-message", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { + @Override + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional<ImageData> imageData) { + var now = purchaseContext.now(ClockProvider.clock()); + var event = (Event) purchaseContext; + TicketCategory ticketCategory = new TicketCategory(0, now, now, 42, "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null, null, null, "CHF", 0, null, TicketCategory.TicketAccessType.INHERIT); + var model = buildModelForTicketEmail(organization, event, sampleTicketReservation(event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(event.getZoneId()), ticketCategory, Map.of()); + model.put("message", "This is your message"); + return model; + } + },; public static final String MULTIPART_ALTERNATIVE_MIMETYPE = "multipart/alternative"; @@ -228,6 +276,8 @@ public Map<String, Object> prepareSampleModel(Organization organization, Event e private static final String HTML_TEMPLATE_SUFFIX = "-html.ms"; private static final String TICKET_KEY = "ticket"; + private static final String RESERVATION_ID = "reservationId"; + private static final String RESERVATION_ID_VALUE = "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e"; private final String classPathUrl; @@ -235,12 +285,20 @@ public Map<String, Object> prepareSampleModel(Organization organization, Event e private final boolean overridable; private final String renderedContentType; private final TemplateManager.TemplateOutput templateOutput; + /** + * PurchaseContextType used for preview + */ + private final PurchaseContextType purchaseContextType; TemplateResource(String classPathUrl, String renderedContentType, TemplateManager.TemplateOutput templateOutput) { + this(classPathUrl, renderedContentType, templateOutput, PurchaseContextType.event); + } + TemplateResource(String classPathUrl, String renderedContentType, TemplateManager.TemplateOutput templateOutput, PurchaseContextType purchaseContextType) { this.classPathUrl = classPathUrl; this.overridable = true; this.renderedContentType = renderedContentType; this.templateOutput = templateOutput; + this.purchaseContextType = purchaseContextType; } @@ -272,7 +330,11 @@ public TemplateManager.TemplateOutput getTemplateOutput() { return templateOutput; } - public Map<String, Object> prepareSampleModel(Organization organization, Event event, Optional<ImageData> imageData) { + public PurchaseContextType getPurchaseContextType() { + return purchaseContextType; + } + + public Map<String, Object> prepareSampleModel(Organization organization, PurchaseContext event, Optional<ImageData> imageData) { return Collections.emptyMap(); } @@ -283,9 +345,9 @@ private static Ticket sampleTicket(ZoneId zoneId) { private static Map<String, Object> sampleBillingDocument(Optional<ImageData> imageData, Organization organization, Event event) { Map<String, Object> model = prepareSampleDataForConfirmationEmail(organization, event); imageData.ifPresent(iData -> { - model.put("eventImage", iData.getEventImage()); - model.put("imageWidth", iData.getImageWidth()); - model.put("imageHeight", iData.getImageHeight()); + model.put("eventImage", iData.eventImage()); + model.put("imageWidth", iData.imageWidth()); + model.put("imageHeight", iData.imageHeight()); }); return model; } @@ -297,14 +359,14 @@ private static TicketCategory sampleCategory(ZoneId zoneId) { } private static Ticket sampleTicket(String firstName, String lastName, String email, ZoneId zoneId) { - return new Ticket(0, "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), 0, "ACQUIRED", 0, - "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", firstName + " " + lastName, firstName, lastName, email, false, "en", + return new Ticket(0, RESERVATION_ID_VALUE, ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), 0, "ACQUIRED", 0, + RESERVATION_ID_VALUE, firstName + " " + lastName, firstName, lastName, email, false, "en", 1000, 1000, 80, 0, null, "CHF", List.of(), null, PriceContainer.VatStatus.INCLUDED); } private static TicketReservation sampleTicketReservation(ZoneId zoneId) { var clock = ClockProvider.clock().withZone(zoneId); - return new TicketReservation("597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", new Date(), TicketReservation.TicketReservationStatus.COMPLETE, + return new TicketReservation(RESERVATION_ID_VALUE, new Date(), TicketReservation.TicketReservationStatus.COMPLETE, "Firstname Lastname", "FirstName", "Lastname", "email@email.tld", "billing address", ZonedDateTime.now(clock), ZonedDateTime.now(clock), PaymentProxy.STRIPE, true, null, false, "en", false, null, null, null, "123456", "CH", false, new BigDecimal("8.00"), true, @@ -316,7 +378,7 @@ private static Map<String, Object> prepareSampleDataForConfirmationEmail(Organiz Optional<String> vat = Optional.of("VAT-NR"); List<TicketWithCategory> tickets = Collections.singletonList(new TicketWithCategory(sampleTicket(event.getZoneId()), sampleCategory(event.getZoneId()))); OrderSummary orderSummary = new OrderSummary(new TotalPrice(1000, 80, 0, 0, "CHF"), - List.of(new SummaryRow("Ticket", "10.00", "9.20", 1, "9.20", "9.20", 1000, SummaryRow.SummaryType.TICKET, null)), false, "10.00", "0.80", false, false, false, "8", PriceContainer.VatStatus.INCLUDED, "1.00"); + List.of(new SummaryRow("Ticket", "10.00", "9.20", 1, "9.20", "9.20", 1000, SummaryRow.SummaryType.TICKET, null, PriceContainer.VatStatus.INCLUDED)), false, "10.00", "0.80", false, false, false, "8", PriceContainer.VatStatus.INCLUDED, "1.00"); String baseUrl = "http://your-domain.tld"; String reservationUrl = baseUrl + "/reservation-url/"; String reservationShortId = "597e7e7b"; @@ -326,7 +388,7 @@ private static Map<String, Object> prepareSampleDataForConfirmationEmail(Organiz private static Map<String, Object> prepareSampleDataForChargeFailed(Organization organization, Event event) { TicketReservation reservation = sampleTicketReservation(event.getZoneId()); return Map.of( - "reservationId", reservation.getId().substring(0, 8), + RESERVATION_ID, reservation.getId().substring(0, 8), "reservationCancelled", true, "reservation", reservation, "eventName", event.getDisplayName(), @@ -515,7 +577,7 @@ public static Map<String, Object> buildModelForTicketHasBeenCancelledAdmin(Organ model.put("additionalServices", additionalServiceItems.stream() .map(asi -> { Map<String, Object> data = new HashMap<>(); - data.put("name", titleFinder.apply(asi).map(AdditionalServiceText::getValue).orElse("N/A")); + data.put("name", titleFinder.apply(asi).map(AdditionalServiceText::value).orElse("N/A")); data.put("amount", MonetaryUtil.centsToUnit(asi.getFinalPriceCts(), asi.getCurrencyCode()).toString() + event.getCurrency()); data.put("id", asi.getId()); return data; @@ -532,7 +594,7 @@ public static Map<String, Object> buildModelForTicketPDF(Organization organizati Optional<ImageData> imageData, String reservationId, Map<String,String> additionalFields) { - String qrCodeText = ticketWithMetadata.getTicket().ticketCode(event.getPrivateKey()); + String qrCodeText = ticketWithMetadata.getTicket().ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()); // Map<String, Object> model = new HashMap<>(); model.put(TICKET_KEY, ticketWithMetadata.getTicket()); @@ -540,7 +602,7 @@ public static Map<String, Object> buildModelForTicketPDF(Organization organizati model.put("ticketCategory", ticketCategory); model.put("event", event); model.put("organization", organization); - model.put("reservationId", reservationId); + model.put(RESERVATION_ID, reservationId); model.put(ADDITIONAL_FIELDS_KEY, additionalFields); model.put(METADATA_ATTRIBUTES_KEY, ticketWithMetadata.getAttributes()); fillTicketValidity(event, ticketCategory, model); @@ -548,15 +610,43 @@ public static Map<String, Object> buildModelForTicketPDF(Organization organizati model.put("qrCodeDataUri", "data:image/png;base64," + Base64.getEncoder().encodeToString(createQRCode(qrCodeText))); imageData.ifPresent(iData -> { - model.put("eventImage", iData.getEventImage()); - model.put("imageWidth", iData.getImageWidth()); - model.put("imageHeight", iData.getImageHeight()); + model.put("eventImage", iData.eventImage()); + model.put("imageWidth", iData.imageWidth()); + model.put("imageHeight", iData.imageHeight()); }); model.put("deskPaymentRequired", Optional.ofNullable(ticketReservation.getPaymentMethod()).orElse(PaymentProxy.STRIPE).isDeskPaymentRequired()); return model; } + public static Map<String, Object> buildModelForSubscriptionPDF(Subscription subscription, + SubscriptionDescriptor subscriptionDescriptor, + Organization organization, + SubscriptionMetadata metadata, + Optional<ImageData> imageData, + String reservationId, + Locale locale, + TicketReservation reservation) { + Map<String, Object> model = new HashMap<>(); + model.put("title", subscriptionDescriptor.getLocalizedTitle(locale)); + model.put("validityTypeNotSet", subscriptionDescriptor.getValidityType() == SubscriptionValidityType.NOT_SET); + model.put("validityTypeStandard", subscriptionDescriptor.getValidityType() == SubscriptionValidityType.STANDARD); + model.put("validityTypeCustom", subscriptionDescriptor.getValidityType() == SubscriptionValidityType.CUSTOM); + model.put("subscription", subscription); + model.put(SUBSCRIPTION_DESCRIPTOR_ATTRIBUTE, subscriptionDescriptor); + model.put("organization", organization); + model.put("reservation", reservation); + model.put(RESERVATION_ID, reservationId); + model.put(METADATA_ATTRIBUTES_KEY, metadata.getProperties()); + model.put("displayPin", metadata.getConfiguration().isDisplayPin()); + imageData.ifPresent(iData -> { + model.put("logo", iData.eventImage); + model.put("imageWidth", iData.imageWidth); + model.put("imageHeight", iData.imageHeight); + }); + return model; + } + // used by WAITING_QUEUE_JOINED public static Map<String, Object> buildModelForWaitingQueueJoined(Organization organization, Event event, CustomerName name) { Map<String, Object> model = new HashMap<>(); @@ -600,21 +690,21 @@ public static ImageData fillWithImageData(FileBlobMetadata m, byte[] image) { return new ImageData(null, null, null); } - @Getter - @AllArgsConstructor - public static class ImageData { - private final String eventImage; - private final Integer imageWidth; - private final Integer imageHeight; + + public record ImageData(String eventImage, Integer imageWidth, Integer imageHeight) { } @Getter - @AllArgsConstructor public static class TicketReservationWithZonedExpiringDate { @Delegate private final TicketReservationInfo reservation; private final Event event; + public TicketReservationWithZonedExpiringDate(TicketReservationInfo reservation, Event event) { + this.reservation = reservation; + this.event = event; + } + public ZonedDateTime getZonedExpiration() { return reservation.getValidity().toInstant().atZone(event.getZoneId()); } diff --git a/src/main/java/alfio/util/Validator.java b/src/main/java/alfio/util/Validator.java index 6adde11401..2b326db6fc 100644 --- a/src/main/java/alfio/util/Validator.java +++ b/src/main/java/alfio/util/Validator.java @@ -28,8 +28,6 @@ import alfio.model.result.ErrorCode; import alfio.model.result.Result; import alfio.model.result.ValidationResult; -import lombok.AllArgsConstructor; -import lombok.RequiredArgsConstructor; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -40,7 +38,6 @@ import java.util.*; import java.util.function.Function; import java.util.regex.Pattern; -import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isAnyBlank; @@ -199,7 +196,7 @@ public static ValidationResult evaluateValidationResult(Errors errors) { if (errors.hasFieldErrors()) { return ValidationResult.failed(errors.getFieldErrors() .stream().map(ValidationResult.ErrorDescriptor::fromFieldError) - .collect(Collectors.toList())); + .toList()); } return ValidationResult.success(); } @@ -217,7 +214,6 @@ public static ValidationResult performAdvancedValidation(AdvancedTicketAssignmen return evaluateValidationResult(errors); } - @AllArgsConstructor public static class TicketFieldsFilterer { private final List<TicketFieldConfiguration> additionalFieldsForEvent; @@ -225,6 +221,16 @@ public static class TicketFieldsFilterer { private final Set<Integer> additionalServiceIds; private final Optional<Ticket> firstTicketInReservation; + public TicketFieldsFilterer(List<TicketFieldConfiguration> additionalFieldsForEvent, + Function<String, Integer> fromTicketUUIDToTicketCategoryId, + Set<Integer> additionalServiceIds, + Optional<Ticket> firstTicketInReservation) { + this.additionalFieldsForEvent = additionalFieldsForEvent; + this.fromTicketUUIDToTicketCategoryId = fromTicketUUIDToTicketCategoryId; + this.additionalServiceIds = additionalServiceIds; + this.firstTicketInReservation = firstTicketInReservation; + } + public List<TicketFieldConfiguration> getFieldsForTicket(String ticketUUID) { var isFirstTicket = firstTicketInReservation.map(first -> ticketUUID.equals(first.getUuid())).orElse(false); @@ -238,7 +244,7 @@ private static List<TicketFieldConfiguration> filterFieldsForTicket(List<TicketF return additionalFieldsForEvent.stream() .filter(field -> field.rulesApply(ticketCategoryId)) .filter(f -> f.getContext() == TicketFieldConfiguration.Context.ATTENDEE || (isFirstTicket && Optional.ofNullable(f.getAdditionalServiceId()).filter(additionalServiceIds::contains).isPresent())) - .collect(Collectors.toList()); + .toList(); } } @@ -448,19 +454,27 @@ private static boolean validateDescriptionList(List<EventModification.Additional } public static ValidationResult validateAdditionalFields(List<TicketFieldConfiguration> fieldConf, EventModification.AdditionalField field, Errors errors){ - String duplicateName = fieldConf.stream().filter(f->f.getName().equalsIgnoreCase(field.getName())).map(TicketFieldConfiguration::getName).findAny().orElse(""); + String duplicateName = fieldConf.stream().map(TicketFieldConfiguration::getName) + .filter(name -> name.equalsIgnoreCase(field.getName())) + .findAny() + .orElse(""); if(StringUtils.isNotBlank(duplicateName)){ errors.rejectValue("name", ErrorCode.DUPLICATE); } return evaluateValidationResult(errors); } - @RequiredArgsConstructor + public static class AdvancedTicketAssignmentValidator implements Function<AdvancedValidationContext, Result<Void>> { private final SameCountryValidator vatValidator; private final GroupManager.WhitelistValidator whitelistValidator; + public AdvancedTicketAssignmentValidator(SameCountryValidator vatValidator, GroupManager.WhitelistValidator whitelistValidator) { + this.vatValidator = vatValidator; + this.whitelistValidator = whitelistValidator; + } + @Override public Result<Void> apply(AdvancedValidationContext context) { @@ -480,13 +494,25 @@ public Result<Void> apply(AdvancedValidationContext context) { } } - @RequiredArgsConstructor + public static class AdvancedValidationContext { private final UpdateTicketOwnerForm updateTicketOwnerForm; private final List<TicketFieldConfiguration> ticketFieldConfigurations; private final int categoryId; private final String ticketUuid; private final String prefix; + + public AdvancedValidationContext(UpdateTicketOwnerForm updateTicketOwnerForm, + List<TicketFieldConfiguration> ticketFieldConfigurations, + int categoryId, + String ticketUuid, + String prefix) { + this.updateTicketOwnerForm = updateTicketOwnerForm; + this.ticketFieldConfigurations = ticketFieldConfigurations; + this.categoryId = categoryId; + this.ticketUuid = ticketUuid; + this.prefix = prefix; + } } } diff --git a/src/main/java/alfio/util/WorkingDaysAdjusters.java b/src/main/java/alfio/util/WorkingDaysAdjusters.java index 181f5db162..40502968a9 100644 --- a/src/main/java/alfio/util/WorkingDaysAdjusters.java +++ b/src/main/java/alfio/util/WorkingDaysAdjusters.java @@ -16,8 +16,6 @@ */ package alfio.util; -import lombok.experimental.UtilityClass; - import java.time.DayOfWeek; import java.time.LocalTime; import java.time.temporal.ChronoUnit; @@ -25,12 +23,14 @@ import java.time.temporal.TemporalAdjuster; import java.util.*; -@UtilityClass -public class WorkingDaysAdjusters { +public final class WorkingDaysAdjusters { private static final Set<DayOfWeek> MON_FRI = EnumSet.complementOf(EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)); private static final List<HoursRange> ALL_DAY = Collections.singletonList(new HoursRange(LocalTime.of(8, 0, 0), LocalTime.of(20, 0, 0))); + private WorkingDaysAdjusters() { + } + public static TemporalAdjuster defaultWorkingDays() { return temporal -> adjust(temporal, MON_FRI, ALL_DAY); } diff --git a/src/main/java/alfio/util/Wrappers.java b/src/main/java/alfio/util/Wrappers.java index cb2b2bad21..23c1fd1ae7 100644 --- a/src/main/java/alfio/util/Wrappers.java +++ b/src/main/java/alfio/util/Wrappers.java @@ -16,17 +16,20 @@ */ package alfio.util; -import lombok.experimental.UtilityClass; -import lombok.extern.log4j.Log4j2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.dao.EmptyResultDataAccessException; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; -@UtilityClass -@Log4j2 -public class Wrappers { +public final class Wrappers { + + private static final Logger log = LoggerFactory.getLogger(Wrappers.class); + + private Wrappers() { + } public static <I> void voidTransactionWrapper(Consumer<I> consumer, I input) { try { diff --git a/src/main/java/alfio/util/checkin/NameNormalizer.java b/src/main/java/alfio/util/checkin/NameNormalizer.java new file mode 100644 index 0000000000..d24fd64781 --- /dev/null +++ b/src/main/java/alfio/util/checkin/NameNormalizer.java @@ -0,0 +1,42 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.util.checkin; + +// heavily inspired by https://stackoverflow.com/a/5470803 +public class NameNormalizer { + + private NameNormalizer() {} + + public static String normalize(String input) { + StringBuilder sb = new StringBuilder(input.length()); + for (int i=0; i < input.length(); i++) { + sb.append(replaceCharIfNotSupported(input.charAt(i))); + } + return sb.toString(); + } + + private static String replaceCharIfNotSupported(char in) { + if (in == 'Σ') { + return "σς"; + } + char lowercase = Character.toLowerCase(in); + if (lowercase == 'ı') { + return "i"; // special case: we render the turkish ı as i since its uppercase is represented as Latin I + } + return Character.toString(lowercase); + } +} diff --git a/src/main/java/alfio/util/checkin/TicketCheckInUtil.java b/src/main/java/alfio/util/checkin/TicketCheckInUtil.java index fc1a720603..cf2791d818 100644 --- a/src/main/java/alfio/util/checkin/TicketCheckInUtil.java +++ b/src/main/java/alfio/util/checkin/TicketCheckInUtil.java @@ -23,23 +23,24 @@ import alfio.model.TicketCategory; import alfio.repository.EventRepository; import alfio.repository.TicketCategoryRepository; -import lombok.experimental.UtilityClass; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; import java.util.function.Supplier; -@UtilityClass -public class TicketCheckInUtil { +public final class TicketCheckInUtil { public static final String CUSTOM_CHECK_IN_URL = "customCheckInUrl"; public static final String ONLINE_CHECK_IN_URL = "onlineCheckInUrl"; public static final String CUSTOM_CHECK_IN_URL_TEXT = "customCheckInUrlText"; public static final String CUSTOM_CHECK_IN_URL_DESCRIPTION = "customCheckInUrlDescription"; + private TicketCheckInUtil() { + } + public static String ticketOnlineCheckInUrl(Event event, Ticket ticket, String baseUrl) { - var ticketCode = DigestUtils.sha256Hex(ticket.ticketCode(event.getPrivateKey())); + var ticketCode = DigestUtils.sha256Hex(ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive())); return StringUtils.removeEnd(baseUrl, "/") + "/event/" + event.getShortName() + "/ticket/" + ticket.getUuid() + "/check-in/"+ticketCode; } @@ -59,7 +60,7 @@ public static Map<String, String> getOnlineCheckInInfo(ExtensionManager extensio if(customMetadataOptional.isPresent()) { var ticketMetadata = customMetadataOptional.get(); var joinLink = ticketMetadata.getJoinLink(); - result.put(ONLINE_CHECK_IN_URL, joinLink.getLink()); + result.put(ONLINE_CHECK_IN_URL, joinLink.link()); if(joinLink.hasLinkText()) { result.put(CUSTOM_CHECK_IN_URL_TEXT, joinLink.getLocalizedText(ticketLanguage.getLanguage(), event)); } diff --git a/src/main/java/alfio/util/oauth2/AccessTokenRequestDetails.java b/src/main/java/alfio/util/oauth2/AccessTokenRequestDetails.java index 105d5b9577..3fec4b8000 100644 --- a/src/main/java/alfio/util/oauth2/AccessTokenRequestDetails.java +++ b/src/main/java/alfio/util/oauth2/AccessTokenRequestDetails.java @@ -16,12 +16,6 @@ */ package alfio.util.oauth2; -import lombok.Data; -@Data -public class AccessTokenRequestDetails { - private final String clientId; - private final String clientSecret; - private final String tokenUrl; - private final String code; +public record AccessTokenRequestDetails(String clientId, String clientSecret, String tokenUrl, String code) { } diff --git a/src/main/java/alfio/util/oauth2/AccessTokenResponseDetails.java b/src/main/java/alfio/util/oauth2/AccessTokenResponseDetails.java index a7f5bacbe9..dc4a23de9e 100644 --- a/src/main/java/alfio/util/oauth2/AccessTokenResponseDetails.java +++ b/src/main/java/alfio/util/oauth2/AccessTokenResponseDetails.java @@ -16,12 +16,8 @@ */ package alfio.util.oauth2; -import lombok.Data; - -@Data -public class AccessTokenResponseDetails { - private final String accessToken; - private final String refreshToken; - private final String errorMessage; - private final boolean success; +public record AccessTokenResponseDetails(String accessToken, + String refreshToken, + String errorMessage, + boolean success) { } diff --git a/src/main/java/alfio/util/oauth2/AuthorizationRequestDetails.java b/src/main/java/alfio/util/oauth2/AuthorizationRequestDetails.java index b4c81a38e1..6983969487 100644 --- a/src/main/java/alfio/util/oauth2/AuthorizationRequestDetails.java +++ b/src/main/java/alfio/util/oauth2/AuthorizationRequestDetails.java @@ -16,10 +16,5 @@ */ package alfio.util.oauth2; -import lombok.Data; - -@Data -public class AuthorizationRequestDetails { - private final String authorizationUrl; - private final String state; +public record AuthorizationRequestDetails(String authorizationUrl, String state) { } diff --git a/src/main/resources/alfio/certificates/AppleWWDRCA.cer b/src/main/resources/alfio/certificates/AppleWWDRCA.cer deleted file mode 100644 index d2bb1da641..0000000000 Binary files a/src/main/resources/alfio/certificates/AppleWWDRCA.cer and /dev/null differ diff --git a/src/main/resources/alfio/certificates/AppleWWDRCAG4.cer b/src/main/resources/alfio/certificates/AppleWWDRCAG4.cer new file mode 100644 index 0000000000..b9f0bf298d Binary files /dev/null and b/src/main/resources/alfio/certificates/AppleWWDRCAG4.cer differ diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.48__ADD_CURRENCY_CODE_TO_PROMOCODE.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.48__ADD_CURRENCY_CODE_TO_PROMOCODE.sql new file mode 100644 index 0000000000..6b55268b9c --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.48__ADD_CURRENCY_CODE_TO_PROMOCODE.sql @@ -0,0 +1,18 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +alter table promo_code add column currency_code varchar(3); \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.1__ADD_RESERVATION_METADATA.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.1__ADD_RESERVATION_METADATA.sql new file mode 100644 index 0000000000..1123487a3d --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.1__ADD_RESERVATION_METADATA.sql @@ -0,0 +1,18 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +alter table tickets_reservation add column metadata jsonb not null default '{}'; \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.2__ADD_SUBSCRIPTION_METADATA.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.2__ADD_SUBSCRIPTION_METADATA.sql new file mode 100644 index 0000000000..20d99d2722 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.2__ADD_SUBSCRIPTION_METADATA.sql @@ -0,0 +1,18 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +alter table subscription add column metadata jsonb not null default '{}'; \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.3__ADDITIONAL_VAT_STATUS.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.3__ADDITIONAL_VAT_STATUS.sql new file mode 100644 index 0000000000..7ee84de81d --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.3__ADDITIONAL_VAT_STATUS.sql @@ -0,0 +1,19 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +alter type VAT_STATUS ADD VALUE 'CUSTOM_NOT_INCLUDED_EXEMPT'; +alter type VAT_STATUS ADD VALUE 'CUSTOM_INCLUDED_EXEMPT'; \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.4__ADMIN_JOB_ALLOW_DUPLICATES.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.4__ADMIN_JOB_ALLOW_DUPLICATES.sql new file mode 100644 index 0000000000..e68be1789d --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.4__ADMIN_JOB_ALLOW_DUPLICATES.sql @@ -0,0 +1,21 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +alter table admin_job_queue add column allow_duplicates char(1) default 'N'; +alter table admin_job_queue drop constraint "unique_job_schedule"; +alter table admin_job_queue add constraint "unique_job_schedule" unique(job_name, request_ts, allow_duplicates); + diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.5__SPONSOR_SCAN_OPERATOR.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.5__SPONSOR_SCAN_OPERATOR.sql new file mode 100644 index 0000000000..61557d91a2 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.5__SPONSOR_SCAN_OPERATOR.sql @@ -0,0 +1,21 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +alter table sponsor_scan add column operator text not null default '__DEFAULT__'; +alter table sponsor_scan drop constraint "spsc_unique_ticket"; +alter table sponsor_scan add constraint "spsc_unique_ticket" unique(event_id, ticket_id, user_id, operator); + diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.6__FIX_OLD_RESERVATIONS.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.6__FIX_OLD_RESERVATIONS.sql new file mode 100644 index 0000000000..62113794a6 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49.6__FIX_OLD_RESERVATIONS.sql @@ -0,0 +1,20 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +update tickets_reservation set metadata = '{"finalized": true, "hideContactData": false, "readyForConfirmation": true}' + where status in ('COMPLETE', 'OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT') and metadata = '{}'; + diff --git a/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49__ADD_SUBSCRIPTION_STATUS.sql b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49__ADD_SUBSCRIPTION_STATUS.sql new file mode 100644 index 0000000000..3aa41c71b9 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V204_2.0.0.49__ADD_SUBSCRIPTION_STATUS.sql @@ -0,0 +1,20 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +create type SUBSCRIPTION_DESCRIPTOR_STATUS as enum ('ACTIVE', 'NOT_ACTIVE'); +alter table subscription_descriptor add column + status SUBSCRIPTION_DESCRIPTOR_STATUS not null default 'ACTIVE'; \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/V205_2.0.0.49__UPDATE_SPRING_SESSION.sql b/src/main/resources/alfio/db/PGSQL/V205_2.0.0.49__UPDATE_SPRING_SESSION.sql new file mode 100644 index 0000000000..da18f360b3 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V205_2.0.0.49__UPDATE_SPRING_SESSION.sql @@ -0,0 +1,39 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +CREATE TABLE ALFIO_SPRING_SESSION ( + PRIMARY_ID CHAR(36) NOT NULL, + SESSION_ID CHAR(36) NOT NULL, + CREATION_TIME BIGINT NOT NULL, + LAST_ACCESS_TIME BIGINT NOT NULL, + MAX_INACTIVE_INTERVAL INT NOT NULL, + EXPIRY_TIME BIGINT NOT NULL, + PRINCIPAL_NAME VARCHAR(100), + CONSTRAINT ALFIO_SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) +); + +CREATE UNIQUE INDEX ALFIO_SPRING_SESSION_IX1 ON ALFIO_SPRING_SESSION (SESSION_ID); +CREATE INDEX ALFIO_SPRING_SESSION_IX2 ON ALFIO_SPRING_SESSION (EXPIRY_TIME); +CREATE INDEX ALFIO_SPRING_SESSION_IX3 ON ALFIO_SPRING_SESSION (PRINCIPAL_NAME); + +CREATE TABLE ALFIO_SPRING_SESSION_ATTRIBUTES ( + SESSION_PRIMARY_ID CHAR(36) NOT NULL, + ATTRIBUTE_NAME VARCHAR(200) NOT NULL, + ATTRIBUTE_BYTES BYTEA NOT NULL, + CONSTRAINT ALFIO_SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), + CONSTRAINT ALFIO_SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES ALFIO_SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE +); \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/V205_2.0.0.50__QR_CASE_INSENSITIVE_MARKER.sql b/src/main/resources/alfio/db/PGSQL/V205_2.0.0.50__QR_CASE_INSENSITIVE_MARKER.sql new file mode 100644 index 0000000000..94592f8317 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V205_2.0.0.50__QR_CASE_INSENSITIVE_MARKER.sql @@ -0,0 +1,18 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +-- this is only meant to be a marker and act as a baseline for applying the new "case insensitive" QR Code generation \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__000_VIEW_drops.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__000_VIEW_drops.sql index 42796789de..77f8f8ef15 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__000_VIEW_drops.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__000_VIEW_drops.sql @@ -30,4 +30,5 @@ drop view if exists basic_event_with_optional_subscription; drop view if exists reservation_and_subscription_and_tx; drop view if exists extension_capabilities; drop view if exists reservation_with_purchase_context; -drop view if exists available_subscriptions_by_event; \ No newline at end of file +drop view if exists available_subscriptions_by_event; +drop view if exists promocode_usage_details; \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__003_VIEW_ticket_category_statistics.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__003_VIEW_ticket_category_statistics.sql index 33c0484145..73705456f7 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__003_VIEW_ticket_category_statistics.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__003_VIEW_ticket_category_statistics.sql @@ -33,7 +33,8 @@ from coalesce(sold_tickets_count,0) as sold_tickets_count, coalesce(released_count, 0) as released_count, case(bounded) when false then 0 else max_tickets - coalesce(sold_tickets_count,0 ) - coalesce(checked_in_count, 0) - coalesce(pending_count, 0) end as not_sold_tickets, - coalesce(stuck_count, 0) as stuck_count + coalesce(stuck_count, 0) as stuck_count, + category_configuration from (select max_tickets, bounded, id, event_id, expiration < now() as is_expired, access_restricted from ticket_category where tc_status = 'ACTIVE' ) ticket_cat @@ -60,5 +61,8 @@ left join where tickets_reservation.status = 'STUCK' group by category_id) stuck_count on ticket_cat.id = stuck_count.category_id +left join +(select ticket_category_id_fk, jsonb_agg(json_build_object('id', id, 'key', c_key, 'value', c_value, 'configurationPathLevel', 'TICKET_CATEGORY')) category_configuration + from configuration_ticket_category group by 1) tc_settings on ticket_cat.id = tc_settings.ticket_category_id_fk ) as res); \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__004_VIEW_events_statistics.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__004_VIEW_events_statistics.sql index 122e0d3e61..5b1c933497 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__004_VIEW_events_statistics.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__004_VIEW_events_statistics.sql @@ -15,15 +15,16 @@ -- along with alf.io. If not, see <http://www.gnu.org/licenses/>. -- -create view events_statistics as (select +create view events_statistics as ( +select event.id, (select count(id) from ticket where event_id = event.id and status not in ('INVALIDATED', 'EXPIRED')) as available_seats, - case(contains_unbounded_categories) when true then 0 else (select count(id) from ticket where event_id = event.id and status not in ('INVALIDATED', 'EXPIRED')) - allocated_count end as not_allocated_tickets, - pending_count as pending_tickets, - sold_tickets_count as sold_tickets, - (select released_count + count(id) from ticket where event_id = event.id and status = 'RELEASED' and category_id is null) as released_tickets, - stats.checked_in_count as checked_in_tickets, - case(contains_unbounded_categories) when true then + coalesce( case(contains_unbounded_categories) when true then 0 else (select count(id) from ticket where event_id = event.id and status not in ('INVALIDATED', 'EXPIRED')) - allocated_count end, 0) as not_allocated_tickets, + coalesce (pending_count, 0) as pending_tickets, + coalesce (sold_tickets_count, 0) as sold_tickets, + coalesce ( (select released_count + count(id) from ticket where event_id = event.id and status = 'RELEASED' and category_id is null),0) as released_tickets, + coalesce(stats.checked_in_count, 0) as checked_in_tickets, + coalesce (case(contains_unbounded_categories) when true then (select count(id) from ticket where event_id = event.id and status not in ('INVALIDATED', 'EXPIRED')) - allocated_count - released_count @@ -31,18 +32,18 @@ create view events_statistics as (select - checked_in_count_unbounded - pending_count_unbounded - (select count(*) from ticket where status = 'RELEASED' and category_id is null and event_id = event.id) - else 0 end as dynamic_allocation, - case (contains_unbounded_categories) when true then - allocated_count - sold_tickets_count_bounded - checked_in_count_bounded - pending_count_bounded - else - allocated_count - sold_tickets_count - stats.checked_in_count - pending_count - end as not_sold_tickets, - is_containing_orphan_tickets_count > 0 as is_containing_orphan_tickets, - is_containing_stuck_tickets_count > 0 as is_containing_stuck_tickets_count, - public_and_valid_count > 0 as show_public_statistics - -from -(select + else 0 end, 0) as dynamic_allocation, + coalesce ( + case (contains_unbounded_categories) when true then + allocated_count - sold_tickets_count_bounded - checked_in_count_bounded - pending_count_bounded + else + allocated_count - sold_tickets_count - stats.checked_in_count - pending_count + end, 0) as not_sold_tickets, + coalesce (is_containing_orphan_tickets_count > 0, false) as is_containing_orphan_tickets, + coalesce (is_containing_stuck_tickets_count > 0, false) as is_containing_stuck_tickets_count, + coalesce (public_and_valid_count > 0, false) as show_public_statistics +from ( +select sum(sold_tickets_count) as sold_tickets_count, sum(checked_in_count) as checked_in_count, sum(pending_count) as pending_count, @@ -59,4 +60,5 @@ from sum(case (is_containing_stuck_tickets) when true then 1 else 0 end) is_containing_stuck_tickets_count, sum(case (access_restricted = false and is_expired = false) when true then 1 else 0 end) as public_and_valid_count, event_id from ticket_category_statistics group by event_id) as stats -inner join event on event_id = event.id order by event.start_ts, event.end_ts); \ No newline at end of file +right outer join event on event_id = event.id order by event.start_ts, event.end_ts +); \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql index 84f35669c9..7d4511e156 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql @@ -121,16 +121,18 @@ create view checkin_ticket_event_and_category_info as e.metadata e_metadata, e.org_id e_org_id, e.locales e_locales, + e.version e_version, (select jsonb_object_agg(tfc.field_name, case tfc.field_type when 'checkbox' then tfv.field_value::jsonb else jsonb_build_array(tfv.field_value) end) as additional_info from ticket_field_value tfv inner join ticket_field_configuration tfc on tfv.ticket_field_configuration_id_fk = tfc.id - where event_id_fk = e.id) tai_additional_info + where event_id_fk = e.id and tfv.ticket_id_fk = t.id) tai_additional_info, + case when t.status = 'ACQUIRED' or t.status = 'TO_BE_PAID' then 0 else 2 end as t_status_priority from ticket t inner join tickets_reservation tr on t.tickets_reservation_id = tr.id inner join ticket_category tc on t.category_id = tc.id inner join event e on e.id = t.event_id - where t.status in ('ACQUIRED', 'CHECKED_IN') + where t.status in ('ACQUIRED', 'CHECKED_IN', 'TO_BE_PAID') and t.first_name is not null and (t.first_name <> '') IS TRUE and t.last_name is not null diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__012_VIEW_subscription_descriptor_statistics.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__012_VIEW_subscription_descriptor_statistics.sql index 58619a68a4..6fbcf6b0b1 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__012_VIEW_subscription_descriptor_statistics.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__012_VIEW_subscription_descriptor_statistics.sql @@ -48,5 +48,7 @@ create view subscription_descriptor_statistics as ( (select count(*) from subscription where status between 'ACQUIRED' and 'CHECKED_IN' and subscription_descriptor_fk = sd.id) s_sold_count, (select count(*) from subscription where status = 'PENDING' and subscription_descriptor_fk = sd.id) s_pending_count, (select count(*) from subscription_event where subscription_descriptor_id_fk = sd.id) s_events_count - from subscription_descriptor sd order by on_sale_from, on_sale_to nulls last + from subscription_descriptor sd + where sd.status = 'ACTIVE' + order by on_sale_from, on_sale_to nulls last ) \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__016_VIEW_promocode_usage_details.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__016_VIEW_promocode_usage_details.sql new file mode 100644 index 0000000000..7efa613da2 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__016_VIEW_promocode_usage_details.sql @@ -0,0 +1,57 @@ +-- +-- This file is part of alf.io. +-- +-- alf.io is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- alf.io is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with alf.io. If not, see <http://www.gnu.org/licenses/>. +-- + +create view promocode_usage_details as ( +with tickets as ( + select t.tickets_reservation_id, json_agg(jsonb_build_object( + 'id', t.uuid, + 'firstName', t.first_name, + 'lastName', t.last_name, + 'type', tc.name, + 'status', t.status + )) items + from ticket t + join ticket_category tc on t.category_id = tc.id + group by t.tickets_reservation_id +), +reservations as ( + select pc.promo_code, tr.event_id_fk, + jsonb_agg(jsonb_build_object( + 'id', tr.id, + 'invoiceNumber', tr.invoice_number, + 'firstName', tr.first_name, + 'lastName', tr.last_name, + 'email', tr.email_address, + 'paymentType', tr.payment_method, + 'finalPriceCts', tr.final_price_cts, + 'currency', tr.currency_code, + 'confirmationTimestamp', to_char(tr.confirmation_ts at time zone 'UTC', 'YYYY-MM-DD') || 'T' || to_char(tr.confirmation_ts at time zone 'UTC', 'HH24:MI:SS.MSZ'), + 'tickets', t.items + )) as agg + from tickets_reservation tr + join promo_code pc ON tr.promo_code_id_fk = pc.id and + pc.organization_id_fk = tr.organization_id_fk and + (pc.event_id_fk is null or tr.event_id_fk = pc.event_id_fk) + join tickets t on t.tickets_reservation_id = tr.id + where tr.promo_code_id_fk is not null and tr.status in ('OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT', 'COMPLETE', 'CANCELLED') + group by pc.promo_code, tr.event_id_fk +) +select r.promo_code, e.id as event_id, e.short_name event_short_name, e.display_name event_display_name, r.agg reservations +from event e + join reservations r on r.event_id_fk = e.id + order by 1 +); diff --git a/src/main/resources/alfio/i18n/README.md b/src/main/resources/alfio/i18n/README.md index 0ff2f8f5c9..1d17016b65 100644 --- a/src/main/resources/alfio/i18n/README.md +++ b/src/main/resources/alfio/i18n/README.md @@ -2,22 +2,26 @@ ### How it works -We use [POEditor](https://poeditor.com/join/project/ttBYTmPYdr) for translating this project in additional languages (including yours!). +We use POEditor for translating this project in additional languages (including yours!). POEditor is so kind to offer us an unlimited open source subscription. **please be advised that we won't accept Pull Requests for translations.** We consider POEditor as our main channel for this kind of modifications. +Here's how you can become a contributor: + #### 1. Tell us about yourself and explain how you can contribute to the project Get in touch with us using our [message board](https://github.com/alfio-event/alf.io/discussions?discussions_q=category%3Atranslations). Tell us how you want to help us, and the languages you're interested in. +This is **mandatory**, as we won't accept "random" requests on POEditor + #### 2. Request access to the POEditor project In order to start contributing, please sign up using [this link](https://poeditor.com/join/project/ttBYTmPYdr) and request to join a translation team. If you don't see your language among the list, please drop us a message using the dedicated [message board](https://github.com/alfio-event/alf.io/discussions?discussions_q=category%3Atranslations). -You can request access on POEditor to any languages except the following: +You can request access on POEditor **to any languages except the following**: - English (main language) - Italian (the core team's mother tongue) diff --git a/src/main/resources/alfio/i18n/public.properties b/src/main/resources/alfio/i18n/public.properties index e80240443e..9b3cbdfd82 100644 --- a/src/main/resources/alfio/i18n/public.properties +++ b/src/main/resources/alfio/i18n/public.properties @@ -15,6 +15,7 @@ common.edit=Edit common.confirm=Confirm locale=en + event.get-your-ticket-for=Get your tickets for {0} event-days.same-day={0} from {1} to {2} @@ -37,6 +38,7 @@ show-subscription.sold-out.message=We''re sorry but currently we don''t have any show-subscription.header.title=Buy a {0} subscription show-event.tickets.left={0} left show-event.category.quantity=Quantity +show-event.additional.custom-amount=Amount show-event.by=By show-event.tickets=Tickets show-event.additional-services=Additional options @@ -146,6 +148,7 @@ reservation-page-complete.ticket-type=Ticket type\: reservation-page-complete.ticket-not-assigned=not yet assigned reservation-page-complete.ticket-nr=Ticket # reservation-page-complete.order-information=Order information: {0} by {1} +reservation-page-complete.reservation.finalization-in-progress=Reservation is being processed. You''ll receive an email as soon as processing is complete. reservation-page-complete.job-title=Job title reservation-page-complete.company=Company @@ -253,6 +256,7 @@ receipt.payment-for=Payment for: #invoice invoice.vat-invoice={0} Invoice +invoice.proforma=Proforma {0} Invoice invoice.credit-note=Credit Note invoice.credit-note.number=Credit Note Number: invoice.invoice=Invoice @@ -596,4 +600,129 @@ time.extended.pattern=HH:mm online.check-in.waiting-room.button=Try again online.check-in.waiting-room.event-started=The event has started but we could not let you in. online.check-in.waiting-room.event-started.contact-organizers=Please try again by clicking on the button below or contact the organizers. -online.check-in.waiting-room.event-ended=Sorry, this event has ended. \ No newline at end of file +online.check-in.waiting-room.event-ended=Sorry, this event has ended. +qr.code=QR Code +link.new-tab=(Opens in a new tab) + + +## admin related keys + +admin.common.add-new=add new +admin.common.name=Name +admin.common.username=Username +admin.common.save=Save +admin.common.cancel=Cancel +admin.common.delete=Delete +admin.common.description=Description +admin.menu.events=Events +admin.menu.subscriptions=Subscriptions +admin.menu.organization=Organization +admin.menu.security=Security +admin.menu.groups=Groups +admin.menu.settings=Configuration +admin.menu.system=System +admin.menu.system.settings=Configuration +admin.menu.system.organizations=Organizations +admin.menu.system.security=Security +admin.dashboard.my-events=My events +admin.dashboard.export-reservations=Export reservations +admin.dashboard.active=Active +admin.dashboard.past=Past +admin.organization.select=Select organization +admin.organization.list.title=Organizations +admin.organization.new.title=Add a new organization +admin.organization.new.organizer-data=Organizer Data +admin.organization.new.advanced-configuration=Advanced Configuration +admin.organization.new.slug=Slug +admin.organization.new.external-id=External ID (open ID organization ID) +admin.user.list.title=Users +admin.user.list.enabled=Enabled +admin.user.list.disable=Disabled +admin.user.list.username=Username +admin.user.list.role=Role +admin.user.list.member-of=Member of +admin.user.list.reset-password=Reset password +admin.user.new.title=User +admin.user.new.organization=Organization +admin.user.new.role=Role +admin.api-key.list.title=System API Key +admin.api-key.list.text= System API Key can be used from external application to create/modify/delete organizations and to create API Keys +admin.api-key.list.download-api-keys=Download all API Keys for +admin.api-key.list.bulk-creation=Bulk creation +admin.api-key.list.api-key=Api key +admin.api-key.list.description=Description +admin.api-key.list.role=Role +admin.api-key.list.member-of=Member of +admin.api-key.list.qr-code=QR Code +admin.api-key.list.enabled=Enable +admin.api-key.list.disable=Disable +admin.api-key.new.title=User +admin.api-key.new.organization=Organization +admin.api-key.new.role=Role +admin.api-key.new.description=Description + +admin.event-dashboard.total-tickets-confirmed=Total tickets confirmed +admin.event-dashboard.tickets-pending=Tickets pending +admin.event-dashboard.gross-income=Gross income +admin.event-dashboard.logistics-info-and-description=Logistic Info and description +admin.event-dashboard.public-url=Public URL +admin.event-dashboard.organized-by=Organized by +admin.event-dashboard.website-url=Website URL +admin.event-dashboard.t-and-c-url=T&C URL +admin.event-dashboard.privacy-policy-url=Privacy Policy URL +admin.event-dashboard.location=Location +admin.event-dashboard.languages=Languages +admin.event-dashboard.start-date-time=Start Date/Time +admin.event-dashboard.end-date-time=End Date/Time +admin.event-dashboard.time-zone=Time Zone +admin.event-dashboard.seats-and-payment-info=Seats and payment info +admin.event-dashboard.tickets-price=Tickets Price +admin.event-dashboard.free-of-charge=Free of charge +admin.event-dashboard.payment-methods=Payment methods +admin.event-dashboard.max-tickets=Max tickets +admin.event-dashboard.vat=VAT (%) +admin.event-dashboard.final-price=Final Price +admin.event-dashboard.categories=Categories +admin.event-dashboard.categories-legend=Here the categories that have been defined for this event. +admin.event-dashboard.active=Active +admin.event-dashboard.expired=Expired +admin.event-dashboard.alert-warning=No categories matching the selected criteria have been found. +admin.event-dashboard.online=Online +admin.event-dashboard.tickets=Tickets +admin.event-dashboard.tokens=Tokens +admin.event-dashboard.options=Options +admin.event-dashboard.send-invitations=Send invitations +admin.event-dashboard.configure=Configure +admin.event-dashboard.alert-message-limited-group=Access to this category has been limited to Group +admin.event-dashboard.alert-message-category-tickets-unknown-state=This category contains tickets whose reservations are in an unknown state. This could happen when we have troubles after receiving a response from the Payment Gateway. Please +admin.event-dashboard.alert-message-category-tickets-unknown-state-link=fix them +admin.event-dashboard.edit-category=Edit category +admin.event-dashboard.message-orphans=This category contains allocated tickets that cannot be sold anymore. Move them to another category: +admin.event-dashboard.apply=apply +admin.event-dashboard.assign-to-dynamic-categories=Assign to dynamic categories +admin.event-dashboard.message-token-generation=Token generation in progress. Please wait 30s and then reload the page. + +admin.event.menu.details=Details +admin.event.menu.attendees-data-to-collect=Attendees'' data to collect +admin.event.menu.promo-codes=Promo Codes +admin.event.menu.polls=Polls +admin.event.menu.reservations=Reservations +admin.event.menu.email-log=E-mail log +admin.event.menu.status=Status +admin.event.menu.waiting-list=Waiting list +admin.event.menu.back-to-event-detail=back to event detail +admin.event.menu.actions=Actions +admin.event.menu.create-reservation=Create reservation +admin.event.menu.import-attendees=Import attendees +admin.event.menu.edit-configuration=Edit configuration +admin.event.menu.customize-templates=Customize templates +admin.event.menu.compose-message=Compose message +admin.event.menu.hide-from-list=Hide from list +admin.event.menu.check-in=Check in +admin.event.menu.copy-this-event=Copy this event +admin.event.menu.download=Download +admin.event.menu.attendees-data=Attendees'' data + +admin.event.menu.sponsors-scan=Sponsors scan +admin.event.menu.all-billing-documents-pdf=All billing documents (PDF) +admin.event.menu.all-billing-excel=All Billing Documents (Excel) diff --git a/src/main/resources/alfio/i18n/public_it.properties b/src/main/resources/alfio/i18n/public_it.properties index 68ba8e7f9b..2b1afb0bce 100644 --- a/src/main/resources/alfio/i18n/public_it.properties +++ b/src/main/resources/alfio/i18n/public_it.properties @@ -1,4 +1,4 @@ -breadcrumb.step1 = Biglietti +breadcrumb.step1 = Seleziona breadcrumb.step2 = Dati di Contatto breadcrumb.step3 = Pagamento breadcrumb.step3.free = Sommario @@ -187,6 +187,7 @@ invoice.payment-instruction = Indicazioni per il pagamento invoice.payment-instruction-no-later-than = Per confermare la tua prenotazione, dobbiamo ricevere il pagamento <strong>entro il {0}</strong>. invoice.payment-reason = Specificando <strong>{0}</strong> quale causale del pagamento. invoice.payment-to = Pagamento a\: +invoice.proforma = Fattura Proforma invoice.refund = Questa fattura è stata aggiornata dopo la cancellazione di uno o più biglietti ed il rimborso di {0}. invoice.refund.line-item = Rimborso invoice.regards = Cordiali saluti, @@ -202,6 +203,8 @@ invoice.vat-invoice = Fattura # please do not translate. This is strictly related to the Italian market invoice.vat-not-added = {0} non inclusa come da direttiva sulla Scissione dei Pagamenti invoice.vat-voided = {0} omessa, come da direttive UE +# Fuzzy +link.new-tab = (si apre in un nuovo tab) locale = it my-orders.confirmation-date = Data di conferma my-orders.description = Qui puoi trovare la lista delle tue prenotazioni @@ -286,6 +289,7 @@ reservation-page-complete.phone-number = Numero di telefono reservation-page-complete.please-check-input-fields = Controlla i valori immessi. reservation-page-complete.release-button.text = Rinuncia reservation-page-complete.resend-reservation-email = Invia email prenotazione +reservation-page-complete.reservation.finalization-in-progress = La prenotazione è in fase di conferma. Riceverai una mail non appena il processo è completo. reservation-page-complete.send-ticket-by-email-to = Invia reservation-page-complete.show-ticket = Visualizza reservation-page-complete.subscription = Dettagli Abbonamento @@ -443,6 +447,7 @@ server-error = Si è verificato un errore inatteso. Per favore segnala quanto ac session-expired.header.title = Sessione scaduta show-event.add-to-calendar = Aggiungi al calendario show-event.additional-services = Opzioni aggiuntive +show-event.additional.custom-amount = Importo # show-event.ms show-event.by = Organizzato da show-event.category.quantity = Quantità diff --git a/src/main/resources/alfio/mjml/confirmation-email-for-organizer-html.mjml b/src/main/resources/alfio/mjml/confirmation-email-for-organizer-html.mjml index 9ee0e23876..ab8161da61 100644 --- a/src/main/resources/alfio/mjml/confirmation-email-for-organizer-html.mjml +++ b/src/main/resources/alfio/mjml/confirmation-email-for-organizer-html.mjml @@ -109,5 +109,19 @@ </mj-text> </mj-column> </mj-section> + <mj-raw>{{#hasMailFooter}}</mj-raw> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + <mj-column width="100%"> + <mj-text align="center" font-size="14px"> + <div style="white-space: pre;">{{mailFooter}}</div> + </mj-text> + </mj-column> + </mj-section> + <mj-raw>{{/hasMailFooter}}</mj-raw> </mj-body> </mjml> \ No newline at end of file diff --git a/src/main/resources/alfio/mjml/confirmation-email-html.mjml b/src/main/resources/alfio/mjml/confirmation-email-html.mjml index 8a39dd9897..7282278c4b 100644 --- a/src/main/resources/alfio/mjml/confirmation-email-html.mjml +++ b/src/main/resources/alfio/mjml/confirmation-email-html.mjml @@ -61,12 +61,13 @@ <mj-section background-color="white"> <mj-column width="100%"> <mj-text align="left" font-size="14px"> - {{#i18n}}email.hello [{{ticketReservation.fullName}}]{{/i18n}}<br/>{{custom-header-text}} + {{#i18n}}email.hello [{{ticketReservation.fullName}}]{{/i18n}} </mj-text> <mj-text align="left" font-size="14px"> {{^orderSummary.waitingForPayment}}{{#i18n}}email-confirmation.completed [{{event.displayName}}] [<a href="{{reservationUrl}}" rel="notrack">{{reservationUrl}}</a>]{{/i18n}}{{/orderSummary.waitingForPayment}} {{#orderSummary.waitingForPayment}}{{#i18n}}email-confirmation.waiting-for-payment [{{event.displayName}}] [<a href="{{reservationUrl}}" rel="notrack">{{reservationUrl}}</a>]{{/i18n}}{{/orderSummary.waitingForPayment}} + {{#render-markdown}}{{custom-header-text}}.html{{/render-markdown}} </mj-text> <mj-button rel="notrack" background-color="#28a745" href="{{reservationUrl}}"> {{#i18n}}email-confirmation.view-reservation{{/i18n}} @@ -87,17 +88,26 @@ <mj-table padding="0 15px"> {{#orderSummary.summary}} + <tr class="bottom-border"> - <th class="header">{{#i18n}}email-confirmation.summary.category{{/i18n}}</th> + <th class="header"> + {{^discount}}{{#i18n}}email-confirmation.summary.category{{/i18n}}{{/discount}} + {{#discount}}{{#i18n}}show-event.promo-code-applied [{{discountCodeDescription}}]{{/i18n}}{{/discount}} + </th> <td>{{name}}</td> </tr> + {{^discount}} <tr class="bottom-border"> <th class="header">{{#i18n}}email-confirmation.summary.quantity{{/i18n}}</th> <td>{{amount}}</td> </tr> + {{/discount}} {{^orderSummary.free}} <tr class="bottom-border"> - <th>{{#i18n}}email-confirmation.summary.subtotal{{/i18n}}</th> + <th> + {{^discount}}{{#i18n}}email-confirmation.summary.subtotal{{/i18n}}{{/discount}} + {{#discount}}{{#i18n}}reservation.dynamic.discount.description{{/i18n}}{{/discount}} + </th> <td>{{subTotal}} {{event.currency}}</td> </tr> {{/orderSummary.free}} @@ -105,6 +115,10 @@ {{^orderSummary.free}}{{^ticketReservation.vatIncluded}} <tr class="bottom-border"> + <th>{{#i18n}}email-confirmation.summary.subtotal{{/i18n}}</th> + <td>{{orderSummary.priceBeforeTaxes}} {{event.currency}}</td> + </tr> + <tr class="bottom-border"> <th>{{#i18n}}reservation-page.vat [{{ticketReservation.usedVatPercent}}] [{{vatTranslation}}]{{/i18n}}</th> <td>{{orderSummary.totalVAT}} {{event.currency}}</td> </tr> @@ -187,9 +201,12 @@ <mj-raw>{{^event.sameDay}}</mj-raw> <mj-table font-size="16px" font-weight="bold" > - <tr class="bottom-border"> + <tr> <td>{{#i18n}}event-days.not-same-day [{{#format-date}}{{event.begin}} EEEE dd MMMM yyyy locale:{{#i18n}}locale{{/i18n}}{{/format-date}}] [{{#format-date}}{{event.begin}} HH:mm{{/format-date}}]{{/i18n}}</td> </tr> + <tr> + <td>{{#i18n}}subscription.detail.validity.CUSTOM.to{{/i18n}}</td> + </tr> <tr> <td>{{#i18n}}event-days.not-same-day [{{#format-date}}{{event.end}} EEEE dd MMMM yyyy locale:{{#i18n}}locale{{/i18n}}{{/format-date}}] [{{#format-date}}{{event.end}} HH:mm (z){{/format-date}}]{{/i18n}}</td> </tr> @@ -207,5 +224,19 @@ </mj-column> </mj-section> <mj-raw>{{/custom-footer-text?}}</mj-raw> + <mj-raw>{{#hasMailFooter}}</mj-raw> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + <mj-column width="100%"> + <mj-text align="center" font-size="14px"> + <div style="white-space: pre;">{{#render-markdown}}{{mailFooter}}.html{{/render-markdown}}</div> + </mj-text> + </mj-column> + </mj-section> + <mj-raw>{{/hasMailFooter}}</mj-raw> </mj-body> </mjml> \ No newline at end of file diff --git a/src/main/resources/alfio/mjml/confirmation-email-subscription-html.mjml b/src/main/resources/alfio/mjml/confirmation-email-subscription-html.mjml index 8991d384ad..66ecc159b8 100644 --- a/src/main/resources/alfio/mjml/confirmation-email-subscription-html.mjml +++ b/src/main/resources/alfio/mjml/confirmation-email-subscription-html.mjml @@ -137,5 +137,19 @@ </mj-column> </mj-section> <mj-raw>{{/custom-footer-text?}}</mj-raw> + <mj-raw>{{#hasMailFooter}}</mj-raw> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + <mj-column width="100%"> + <mj-text align="center" font-size="14px"> + <div style="white-space: pre;">{{mailFooter}}</div> + </mj-text> + </mj-column> + </mj-section> + <mj-raw>{{/hasMailFooter}}</mj-raw> </mj-body> </mjml> \ No newline at end of file diff --git a/src/main/resources/alfio/mjml/custom-message-html.mjml b/src/main/resources/alfio/mjml/custom-message-html.mjml new file mode 100644 index 0000000000..6a483f4a55 --- /dev/null +++ b/src/main/resources/alfio/mjml/custom-message-html.mjml @@ -0,0 +1,110 @@ +<mjml> + <mj-head> + <mj-font name="Source Sans Pro" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" /> + <mj-attributes> + <mj-all font-family="Source Sans Pro, Helvetica" /> + </mj-attributes> + <mj-style> + body { + background: white; + color: #393939; + } + .content-section { + background: white; + } + a { + color: #0056b3; + text-decoration: none; + } + a:hover { + color: #0056b3; + text-decoration: underline; + } + + @media (prefers-color-scheme: dark) { + body { + background: black; + color: #ccc; + } + a { + color: inherit; + text-decoration: none; + } + a:hover { + color: inherit; + text-decoration: underline; + } + } + + tr { + line-height:2; + text-align:center; + } + + tr .bottom-border { + border-bottom:2px solid #ecedee; + } + + li { + margin-top: 10px; + } + img.wallet { + padding-top: 20px; + } + </mj-style> + </mj-head> + <mj-body> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + </mj-section> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="50%"> + <mj-image src="{{baseUrl}}/file/{{event.fileBlobId}}" alt="{{event.displayName}}"></mj-image> + </mj-column> + </mj-section> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-text align="center" font-family="Helvetica Neue" font-size="20px" color="grey" font-weight="200"> + {{event.displayName}} + </mj-text> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + </mj-section> + + <mj-section background-color="white"> + <mj-column width="100%"> + <mj-text font-size="16px"> + {{#render-markdown}}{{message}}.html{{/render-markdown}} + </mj-text> + </mj-column> + </mj-section> + + <mj-raw>{{#walletEnabled}}</mj-raw> + <mj-section background-color="#f8f8ff"> + <mj-raw>{{#googleWalletEnabled}}</mj-raw> + <mj-column> + <mj-image width="217" css-class="wallet" rel="notrack" src="{{baseUrl}}/resources/images/email/enUS_add_to_google_wallet_wallet-badge.png" alt="Add to Google Wallet" align="center" href="{{baseUrl}}/api/wallet/event/{{event.shortName}}/v1/version/passes/{{ticket.uuid}}"></mj-image> + </mj-column> + <mj-raw>{{/googleWalletEnabled}}</mj-raw> + <mj-raw>{{#appleWalletEnabled}}</mj-raw> + <mj-column> + <mj-image width="193" css-class="wallet" rel="notrack" src="{{baseUrl}}/resources/images/email/add-to-apple-wallet-button.png" alt="Add to Apple Wallet" align="center" href="{{baseUrl}}/api/pass/event/{{event.shortName}}/v1/version/passes/{{ticket.uuid}}"></mj-image> + </mj-column> + <mj-raw>{{/appleWalletEnabled}}</mj-raw> + </mj-section> + <mj-raw>{{/walletEnabled}}</mj-raw> + + <mj-section> + <mj-column width="100%"> + <mj-text align="center">{{#i18n}}alfio.credits{{/i18n}}</mj-text> + </mj-column> + </mj-section> + </mj-body> +</mjml> + diff --git a/src/main/resources/alfio/mjml/offline-reservation-expiring-email-for-organizer-html.mjml b/src/main/resources/alfio/mjml/offline-reservation-expiring-email-for-organizer-html.mjml index 50f1791481..72b1f735f1 100644 --- a/src/main/resources/alfio/mjml/offline-reservation-expiring-email-for-organizer-html.mjml +++ b/src/main/resources/alfio/mjml/offline-reservation-expiring-email-for-organizer-html.mjml @@ -67,5 +67,19 @@ </mj-column> </mj-section> + <mj-raw>{{#hasMailFooter}}</mj-raw> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + <mj-column width="100%"> + <mj-text align="center" font-size="14px"> + <div style="white-space: pre;">{{mailFooter}}</div> + </mj-text> + </mj-column> + </mj-section> + <mj-raw>{{/hasMailFooter}}</mj-raw> </mj-body> </mjml> \ No newline at end of file diff --git a/src/main/resources/alfio/mjml/ticket-email-html.mjml b/src/main/resources/alfio/mjml/ticket-email-html.mjml index 83ba0a4d4a..05e9731990 100644 --- a/src/main/resources/alfio/mjml/ticket-email-html.mjml +++ b/src/main/resources/alfio/mjml/ticket-email-html.mjml @@ -44,6 +44,9 @@ tr .bottom-border { border-bottom:2px solid #ecedee; } + img.wallet { + padding-top: 20px; + } </mj-style> </mj-head> <mj-body> @@ -123,8 +126,23 @@ </mj-text> </mj-column> </mj-section> - - <mj-section> + + <mj-raw>{{#walletEnabled}}</mj-raw> + <mj-section background-color="#f8f8ff"> + <mj-raw>{{#googleWalletEnabled}}</mj-raw> + <mj-column> + <mj-image width="199" css-class="wallet" rel="notrack" src="{{baseUrl}}/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.png" alt="Add to Google Wallet" align="center" href="{{baseUrl}}/api/wallet/event/{{event.shortName}}/v1/version/passes/{{ticket.uuid}}"></mj-image> + </mj-column> + <mj-raw>{{/googleWalletEnabled}}</mj-raw> + <mj-raw>{{#appleWalletEnabled}}</mj-raw> + <mj-column> + <mj-image width="193" css-class="wallet" rel="notrack" src="{{baseUrl}}/resources/images/email/add-to-apple-wallet-button.png" alt="Add to Apple Wallet" align="center" href="{{baseUrl}}/api/pass/event/{{event.shortName}}/v1/version/passes/{{ticket.uuid}}"></mj-image> + </mj-column> + <mj-raw>{{/appleWalletEnabled}}</mj-raw> + </mj-section> + <mj-raw>{{/walletEnabled}}</mj-raw> + + <mj-section> <mj-column width="100%"> <mj-button align="center" background-color="#7C8B95" href="{{ticketUrl}}" rel="notrack"> {{#i18n}}email-ticket.view-modify{{/i18n}}</mj-button> </mj-column> @@ -146,6 +164,21 @@ </mj-column> </mj-section> + <mj-raw>{{#hasMailFooter}}</mj-raw> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + <mj-column width="100%"> + <mj-text align="center" font-size="14px"> + <div style="white-space: pre;">{{mailFooter}}</div> + </mj-text> + </mj-column> + </mj-section> + <mj-raw>{{/hasMailFooter}}</mj-raw> + </mj-body> </mjml> diff --git a/src/main/resources/alfio/mjml/ticket-email-online-html.mjml b/src/main/resources/alfio/mjml/ticket-email-online-html.mjml index e72cc01a9b..b532fa74bf 100644 --- a/src/main/resources/alfio/mjml/ticket-email-online-html.mjml +++ b/src/main/resources/alfio/mjml/ticket-email-online-html.mjml @@ -161,5 +161,20 @@ </mj-column> </mj-section> <mj-raw>{{/custom-footer-text}}</mj-raw> + + <mj-raw>{{#hasMailFooter}}</mj-raw> + <mj-section padding-bottom="0px" background-color="white"> + <mj-column width="100%"> + <mj-divider + padding-top="20px" padding-bottom="0px" padding-left="0px" + padding-right="0px" border-width="1px" border-color="#f4f4f4"></mj-divider> + </mj-column> + <mj-column width="100%"> + <mj-text align="center" font-size="14px"> + <div style="white-space: pre;">{{mailFooter}}</div> + </mj-text> + </mj-column> + </mj-section> + <mj-raw>{{/hasMailFooter}}</mj-raw> </mj-body> </mjml> diff --git a/src/main/resources/alfio/templates/charge-failed-txt.ms b/src/main/resources/alfio/templates/charge-failed-txt.ms index 0e766b5d37..dcd104c396 100644 --- a/src/main/resources/alfio/templates/charge-failed-txt.ms +++ b/src/main/resources/alfio/templates/charge-failed-txt.ms @@ -9,4 +9,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/confirmation-email-subscription-txt.ms b/src/main/resources/alfio/templates/confirmation-email-subscription-txt.ms index 09101cfb15..b8a5b62235 100644 --- a/src/main/resources/alfio/templates/confirmation-email-subscription-txt.ms +++ b/src/main/resources/alfio/templates/confirmation-email-subscription-txt.ms @@ -40,4 +40,5 @@ {{custom-footer-text}} {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/confirmation-email-txt.ms b/src/main/resources/alfio/templates/confirmation-email-txt.ms index b5ca892dcf..db608ea695 100644 --- a/src/main/resources/alfio/templates/confirmation-email-txt.ms +++ b/src/main/resources/alfio/templates/confirmation-email-txt.ms @@ -1,8 +1,10 @@ {{#i18n}}email.hello [{{ticketReservation.fullName}}]{{/i18n}} -{{custom-header-text}} + {{^orderSummary.waitingForPayment}}{{#i18n}}email-confirmation.completed [{{event.displayName}}] [{{reservationUrl}}]{{/i18n}}{{/orderSummary.waitingForPayment}} {{#orderSummary.waitingForPayment}}{{#i18n}}email-confirmation.waiting-for-payment [{{event.displayName}}] [{{reservationUrl}}]{{/i18n}}{{/orderSummary.waitingForPayment}} +{{custom-header-text}} + #### {{#i18n}}email-confirmation.reservation-summary{{/i18n}} #### {{#orderSummary.summary}} @@ -42,4 +44,5 @@ reservation.confirmation.email.subscription.button: {{subscriptionUrl}} {{custom-footer-text}} {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/credit-note-issued-email-txt.ms b/src/main/resources/alfio/templates/credit-note-issued-email-txt.ms index 4cbbed4160..bb62e23e8d 100644 --- a/src/main/resources/alfio/templates/credit-note-issued-email-txt.ms +++ b/src/main/resources/alfio/templates/credit-note-issued-email-txt.ms @@ -4,4 +4,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/credit-note.ms b/src/main/resources/alfio/templates/credit-note.ms index 16d4827423..63d80ebc7c 100644 --- a/src/main/resources/alfio/templates/credit-note.ms +++ b/src/main/resources/alfio/templates/credit-note.ms @@ -1,6 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> -<html> +<html lang="{{#i18n}}locale{{/i18n}}"> <head> + <title>{{#i18n}}invoice.credit-note{{/i18n}}</title> + <meta name="subject" content="{{#i18n}}invoice.credit-note{{/i18n}}" /> + <meta name="author" content="{{organization.name}}" /> + <meta name="description" content="{{#i18n}}invoice.credit-note{{/i18n}}" /> <style> @font-face { @@ -104,7 +108,7 @@ <body> <table style="width:100%"> <tr> - {{#eventImage}}<td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}"/></td>{{/eventImage}} + {{#eventImage}}<td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}" alt="{{purchaseContext.displayName}}"/></td>{{/eventImage}} {{^eventImage}}<td style="text-align: center; vertical-align: middle;"><h1>{{purchaseContext.displayName}}</h1></td>{{/eventImage}} <td style="text-align: center; vertical-align: middle;"><h1>{{#i18n}}invoice.credit-note{{/i18n}}</h1></td> </tr> diff --git a/src/main/resources/alfio/templates/custom-message-txt.ms b/src/main/resources/alfio/templates/custom-message-txt.ms new file mode 100644 index 0000000000..a59c45bbb3 --- /dev/null +++ b/src/main/resources/alfio/templates/custom-message-txt.ms @@ -0,0 +1,4 @@ +{{#render-markdown}}{{message}}.text{{/render-markdown}} + +-- +{{#i18n}}alfio.credits{{/i18n}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/invoice.ms b/src/main/resources/alfio/templates/invoice.ms index a8995571c8..7eff441859 100644 --- a/src/main/resources/alfio/templates/invoice.ms +++ b/src/main/resources/alfio/templates/invoice.ms @@ -1,6 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> -<html> +<html lang="{{#i18n}}locale{{/i18n}}"> <head> + <title> + {{#proforma}}{{#i18n}}invoice.proforma [{{vatTranslation}}]{{/i18n}}{{/proforma}} + {{^proforma}}{{#i18n}}invoice.vat-invoice [{{vatTranslation}}]{{/i18n}}{{/proforma}} + </title> + <meta name="subject" content="{{#i18n}}invoice.vat-invoice [{{vatTranslation}}]{{/i18n}}" /> + <meta name="author" content="{{organization.name}}" /> + <meta name="description" content="{{#i18n}}invoice.vat-invoice [{{vatTranslation}}]{{/i18n}}" /> <style> @font-face { @@ -8,6 +15,18 @@ src: url('alfio-internal:/DejaVuSans.ttf'); } + @page { + page: A4; + margin-bottom: 200px; + @bottom-center{content:element(pf);} + } + + .pageFooter { + border-top: 1px solid black; + padding-top: 10px; + position:running(pf); + } + body { font-family: "DejaVu Sans"; font-size:10pt; @@ -42,7 +61,7 @@ margin-bottom:1em; } - table.summary { + table.summary, table.border { border-collapse:collapse; margin-bottom: 20px; border-spacing: 0; @@ -57,7 +76,8 @@ border-bottom: 1px solid #aaa; } - table.summary, table.summary th, table.summary td { + table.summary, table.summary th, table.summary td, + table.border th, table.border td { border: 1px solid #aaa; padding:5px; margin:0; @@ -104,9 +124,14 @@ <body> <table style="width:100%"> <tr> - {{#eventImage}}<td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}"/></td>{{/eventImage}} + {{#eventImage}}<td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}" alt="{{purchaseContext.displayName}}"/></td>{{/eventImage}} {{^eventImage}}<td style="text-align: center; vertical-align: middle;"><h1>{{purchaseContext.displayName}}</h1></td>{{/eventImage}} - <td style="text-align: center; vertical-align: middle;"><h1>{{#i18n}}invoice.vat-invoice [{{vatTranslation}}]{{/i18n}}</h1></td> + <td style="text-align: center; vertical-align: middle;"> + <h1> + {{#proforma}}{{#i18n}}invoice.proforma [{{vatTranslation}}]{{/i18n}}{{/proforma}} + {{^proforma}}{{#i18n}}invoice.vat-invoice [{{vatTranslation}}]{{/i18n}}{{/proforma}} + </h1> + </td> </tr> </table> @@ -195,27 +220,8 @@ </tfoot> </table> - <div class="small {{^isOfflinePayment}}mb{{/isOfflinePayment}}" style="margin-top:1em"> - <p class="strong">{{#i18n}}invoice.order-information{{/i18n}}</p> - <p>{{ticketReservation.id}}</p> - <p>{{#i18n}}invoice.buyer{{/i18n}} {{ticketReservation.fullName}} <{{ticketReservation.email}}></p> - {{#hasRefund}} - <p class="strong">{{#i18n}}invoice.refund [{{purchaseContext.currency}} {{orderSummary.refundedAmount}}]{{/i18n}}</p> - {{/hasRefund}} - </div> - - {{#invoicingAdditionalInfo}} - <pre>{{#italianEInvoicing}} -{{#i18n}}invoice.vat [{{vatTranslation}}]{{/i18n}}: {{ticketReservation.vatNr}} -{{#i18n}}invoice-fields.fiscalCode{{/i18n}}: {{fiscalCode}} -{{#i18n}}invoice-fields.addresseeItalyEInvoice{{/i18n}}: {{referenceType}} -{{#i18n}}invoice-fields.addressee-code{{/i18n}}: {{addresseeCode}} -PEC: {{pec}}{{/italianEInvoicing}} - </pre> - {{/invoicingAdditionalInfo}} - {{#isOfflinePayment}} - <div class="mb" style="margin-top: 1em"> + <div style="margin-top: 1em"> <p>{{#i18n}}invoice.payment-instruction-no-later-than [{{#format-date}}{{expirationDate}} {{#i18n}}datetime.pattern{{/i18n}} locale:{{#i18n}}locale{{/i18n}}{{/format-date}}]{{/i18n}}</p> <br/> <p class="strong">{{#i18n}}invoice.payment-instruction{{/i18n}}</p> @@ -232,9 +238,46 @@ PEC: {{pec}}{{/italianEInvoicing}} <p style="margin-top:0.5em">{{#i18n}}invoice.payment-contact [{{organization.email}}]{{/i18n}}</p> </div> {{/isOfflinePayment}} + <div style="margin-top: 1em"> + <p>{{#i18n}}invoice.regards{{/i18n}}</p> + <p>{{organization.name}}</p> + </div> - <p>{{#i18n}}invoice.regards{{/i18n}}</p> - <p>{{organization.name}}</p> - + <div class="pageFooter"> + <div class="small"> + <p><strong>{{#i18n}}invoice.order-information{{/i18n}}</strong>: {{ticketReservation.id}}, {{#i18n}}invoice.buyer{{/i18n}} {{ticketReservation.fullName}} <{{ticketReservation.email}}></p> + {{#hasRefund}} + <p class="strong">{{#i18n}}invoice.refund [{{purchaseContext.currency}} {{orderSummary.refundedAmount}}]{{/i18n}}</p> + {{/hasRefund}} + </div> + {{#invoicingAdditionalInfo}} + <table style="width:100%;margin-top: 1em" class="border"> + {{#italianEInvoicing}} + <thead> + <tr> + <th class="small">{{#i18n}}invoice.vat [{{vatTranslation}}]{{/i18n}}</th> + <th class="small">{{#i18n}}invoice-fields.fiscalCode{{/i18n}}</th> + <th class="small">{{#i18n}}invoice-fields.addresseeItalyEInvoice{{/i18n}}</th> + <th class="small">{{#i18n}}invoice-fields.addressee-code{{/i18n}}</th> + <th class="small">{{#i18n}}invoice-fields.pec{{/i18n}}</th> + </tr> + </thead> + <tbody> + <tr> + <td class="small">{{ticketReservation.vatNr}}</td> + <td class="small">{{fiscalCode}}</td> + <td class="small">{{referenceType}}</td> + <td class="small">{{addresseeCode}}</td> + <td class="small">{{pec}}</td> + </tr> + </tbody> + {{/italianEInvoicing}} + </table> + {{#italianEInvoicing}} + <!-- the following text is in Italian and is required by law. It will only appear if the italian "e-invoice" data collection is active --> + <p class="small">Documento privo di valenza fiscale ai sensi dell’art. 21 Dpr 633/72. L’originale è disponibile all’indirizzo telematico da Lei fornito oppure nella Sua area riservata dell’Agenzia delle Entrate.</p> + {{/italianEInvoicing}} + {{/invoicingAdditionalInfo}} + </div> </body> </html> \ No newline at end of file diff --git a/src/main/resources/alfio/templates/offline-reservation-expired-email-txt.ms b/src/main/resources/alfio/templates/offline-reservation-expired-email-txt.ms index ceb040dc15..6f6d4484f7 100644 --- a/src/main/resources/alfio/templates/offline-reservation-expired-email-txt.ms +++ b/src/main/resources/alfio/templates/offline-reservation-expired-email-txt.ms @@ -4,4 +4,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/receipt.ms b/src/main/resources/alfio/templates/receipt.ms index 2b7e5391b8..31cd07ba89 100644 --- a/src/main/resources/alfio/templates/receipt.ms +++ b/src/main/resources/alfio/templates/receipt.ms @@ -1,6 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> -<html> +<html lang="{{#i18n}}locale{{/i18n}}"> <head> + <title>{{#i18n}}receipt.receipt{{/i18n}}</title> + <meta name="subject" content="{{#i18n}}receipt.receipt{{/i18n}}" /> + <meta name="author" content="{{organization.name}}" /> + <meta name="description" content="{{#i18n}}receipt.receipt{{/i18n}}" /> <style> body { font-family:"DejaVu Sans Mono"; @@ -37,7 +41,7 @@ <body> <table style="width:100%"> <tr> - {{#eventImage}}<td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}"/></td>{{/eventImage}} + {{#eventImage}}<td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}" alt="{{event.displayName}}"/></td>{{/eventImage}} <td style="text-align: center; vertical-align: middle;"><h1>{{event.displayName}}</h1></td> </tr> </table> diff --git a/src/main/resources/alfio/templates/reminder-email-txt.ms b/src/main/resources/alfio/templates/reminder-email-txt.ms index 472c724e69..463e6b0caf 100644 --- a/src/main/resources/alfio/templates/reminder-email-txt.ms +++ b/src/main/resources/alfio/templates/reminder-email-txt.ms @@ -4,4 +4,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/reminder-ticket-additional-info.ms b/src/main/resources/alfio/templates/reminder-ticket-additional-info.ms index 3796e7285d..888f768fc8 100644 --- a/src/main/resources/alfio/templates/reminder-ticket-additional-info.ms +++ b/src/main/resources/alfio/templates/reminder-ticket-additional-info.ms @@ -4,4 +4,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/reminder-tickets-assignment-email-txt.ms b/src/main/resources/alfio/templates/reminder-tickets-assignment-email-txt.ms index db0f2180a7..0a31e7891b 100644 --- a/src/main/resources/alfio/templates/reminder-tickets-assignment-email-txt.ms +++ b/src/main/resources/alfio/templates/reminder-tickets-assignment-email-txt.ms @@ -4,4 +4,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/send-reserved-code-txt.ms b/src/main/resources/alfio/templates/send-reserved-code-txt.ms index 6d65a217d2..51507d6bc4 100644 --- a/src/main/resources/alfio/templates/send-reserved-code-txt.ms +++ b/src/main/resources/alfio/templates/send-reserved-code-txt.ms @@ -15,4 +15,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/subscription.ms b/src/main/resources/alfio/templates/subscription.ms new file mode 100644 index 0000000000..4c2b35c744 --- /dev/null +++ b/src/main/resources/alfio/templates/subscription.ms @@ -0,0 +1,63 @@ +<html lang="{{#i18n}}locale{{/i18n}}"> + <head> + <title>{{#i18n}}purchase-context.subscription{{/i18n}}</title> + <meta name="subject" content="{{#i18n}}purchase-context.subscription{{/i18n}}" /> + <meta name="author" content="{{organization.name}}" /> + <meta name="description" content="{{#i18n}}purchase-context.subscription{{/i18n}}" /> + <style> + body { + font-family:"DejaVu Sans Mono"; + } + h1,h2,p {margin:0;} + h1 { + font-size:160%; + } + + h2 { + font-size:120%; + font-weight:normal; + border-bottom:1px solid black; + margin-bottom:10px; + } + + table { + margin-bottom:30px; + } + </style> + </head> + <body> + <table style="width:100%"> + <tr> + {{#logo}} + <td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{logo}}" alt=""/></td> + {{/logo}} + <td style="text-align: center; vertical-align: middle;"><h1>{{title}}</h1></td> + </tr> + </table> + + <h2>{{#i18n}}reservation-page-complete.subscription{{/i18n}}</h2> + <table> + <tr> + <td style="width:180px">{{#i18n}}reservation-page-complete.subscription.owner{{/i18n}}:</td> + <td>{{subscription.firstName}} {{subscription.lastName}} <{{subscription.email}}></td> + </tr> + </table> + <h4>{{#subscriptionDescription}}{{/subscriptionDescription}}</h4> + + {{#displayPin}} + <p>{{#i18n}}reservation-page-complete.subscription.pin-description{{/i18n}}</p> + <h3>{{subscription.pin}}</h3> + + <p>{{#i18n}}reservation-page-complete.subscription.id-description{{/i18n}}</p> + <p>{{subscription.id}}</p> + <br><br> + {{/displayPin}} + + <table> + <tr> + <td>{{#i18n}}ticket.order-information{{/i18n}}</td> + <td>{{#i18n}}ticket.order-information-values [{{reservationId}}] [{{reservation.fullName}}]{{/i18n}}</td> + </tr> + </table> + </body> +</html> \ No newline at end of file diff --git a/src/main/resources/alfio/templates/ticket-email-online-txt.ms b/src/main/resources/alfio/templates/ticket-email-online-txt.ms index 6bb2f468b0..6168c8a7c4 100644 --- a/src/main/resources/alfio/templates/ticket-email-online-txt.ms +++ b/src/main/resources/alfio/templates/ticket-email-online-txt.ms @@ -27,5 +27,6 @@ {{#i18n}}email.kind-regards{{/i18n}} {{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} {{#i18n}}alfio.credits{{/i18n}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/ticket-email-txt.ms b/src/main/resources/alfio/templates/ticket-email-txt.ms index 5633c0f825..e030591076 100644 --- a/src/main/resources/alfio/templates/ticket-email-txt.ms +++ b/src/main/resources/alfio/templates/ticket-email-txt.ms @@ -20,5 +20,6 @@ {{#i18n}}email.kind-regards{{/i18n}} {{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} {{#i18n}}alfio.credits{{/i18n}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/ticket-has-been-cancelled-txt.ms b/src/main/resources/alfio/templates/ticket-has-been-cancelled-txt.ms index fb5805c2d1..03a7062f43 100644 --- a/src/main/resources/alfio/templates/ticket-has-been-cancelled-txt.ms +++ b/src/main/resources/alfio/templates/ticket-has-been-cancelled-txt.ms @@ -5,4 +5,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/ticket-has-changed-owner-txt.ms b/src/main/resources/alfio/templates/ticket-has-changed-owner-txt.ms index efb63ce2fe..e291f021ed 100644 --- a/src/main/resources/alfio/templates/ticket-has-changed-owner-txt.ms +++ b/src/main/resources/alfio/templates/ticket-has-changed-owner-txt.ms @@ -5,4 +5,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/ticket.ms b/src/main/resources/alfio/templates/ticket.ms index aa590b4707..ddec71f930 100644 --- a/src/main/resources/alfio/templates/ticket.ms +++ b/src/main/resources/alfio/templates/ticket.ms @@ -1,5 +1,9 @@ -<html> +<html lang="{{#i18n}}locale{{/i18n}}"> <head> + <title>{{#i18n}}ticket.ticket{{/i18n}}</title> + <meta name="subject" content="{{#i18n}}ticket.ticket{{/i18n}}" /> + <meta name="author" content="{{organization.name}}" /> + <meta name="description" content="{{#i18n}}ticket.ticket{{/i18n}}" /> <style> body { font-family:"DejaVu Sans Mono"; @@ -25,7 +29,7 @@ <table style="width:100%"> <tr> {{#eventImage}} - <td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}"/></td> + <td style="width:{{imageWidth}}px; height:{{imageHeight}}px"><img style="margin:auto; width:{{imageWidth}}px; height:{{imageHeight}}px" src="{{eventImage}}" alt=""/></td> {{/eventImage}} <td style="text-align: center; vertical-align: middle;"><h1>{{event.displayName}}</h1></td> </tr> @@ -66,7 +70,7 @@ </tr> </table> </td> - <td style="width:200px;"><div style="text-align:right"><img src="{{qrCodeDataUri}}"/></div></td> + <td style="width:200px;"><div style="text-align:right"><img src="{{qrCodeDataUri}}" alt="{{#i18n}}qr.code{{/i18n}}"/></div></td> </tr> </table> @@ -84,6 +88,12 @@ <td>{{#i18n}}ticket.reference-number{{/i18n}}</td> <td>{{ticket.uuid}}</td> </tr> + {{#hasSubscription}} + <tr> + <td>{{#i18n}}reservation-page.subscription{{/i18n}}</td> + <td>{{subscriptionTitle}}</td> + </tr> + {{/hasSubscription}} <tr> <td>{{#i18n}}ticket.order-information{{/i18n}}</td> <td>{{#i18n}}ticket.order-information-values [{{reservationId}}] [{{reservation.fullName}}]{{/i18n}}</td> diff --git a/src/main/resources/alfio/templates/waiting-queue-joined.ms b/src/main/resources/alfio/templates/waiting-queue-joined.ms index 1c289e3f50..911c810980 100644 --- a/src/main/resources/alfio/templates/waiting-queue-joined.ms +++ b/src/main/resources/alfio/templates/waiting-queue-joined.ms @@ -4,4 +4,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/templates/waiting-queue-reservation-email-txt.ms b/src/main/resources/alfio/templates/waiting-queue-reservation-email-txt.ms index 64c45b7590..954777b8ff 100644 --- a/src/main/resources/alfio/templates/waiting-queue-reservation-email-txt.ms +++ b/src/main/resources/alfio/templates/waiting-queue-reservation-email-txt.ms @@ -18,4 +18,5 @@ {{#i18n}}email.kind-regards{{/i18n}} -{{organization.name}} <{{organization.email}}> \ No newline at end of file +{{organization.name}} <{{organization.email}}> +{{#hasMailFooter}}{{#render-markdown}}{{mailFooter}}.text{{/render-markdown}}{{/hasMailFooter}} \ No newline at end of file diff --git a/src/main/resources/alfio/web-templates/admin-index.ms b/src/main/resources/alfio/web-templates/admin-index.ms index 0fb3ccfd3b..a8dc4809a6 100644 --- a/src/main/resources/alfio/web-templates/admin-index.ms +++ b/src/main/resources/alfio/web-templates/admin-index.ms @@ -19,6 +19,7 @@ <link rel="stylesheet" href="{{contextPath}}/resources/bower_components/dragula.js/dist/dragula.min.css"/> <script nonce="{{nonce}}"> window.ALFIO_CONTEXT_PATH = "{{contextPath}}"; + window.USER_IS_OWNER = {{isOwner}}; </script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/bower_components/jquery/dist/jquery.min.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/bower_components/lodash/lodash.min.js"></script> @@ -67,6 +68,7 @@ <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/promo-codes/promo-codes.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/tickets-remove/tickets-remove.js"></script> + <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/ticket-full-data/ticket-full-data.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/reservation-cancel/reservation-cancel.js"></script> @@ -87,6 +89,7 @@ <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/reservation/view/reservation-view.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/reservations-list/reservations-list.js"></script> + <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/payments-list/payments-list.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/tickets-list/tickets-list.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/label-template/label-template.js"></script> @@ -103,6 +106,7 @@ <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/metadata-editor/metadata-editor.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/project-banner/project-banner.js"></script> <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/polls/polls.js"></script> + <script nonce="{{nonce}}" src="{{contextPath}}/resources/js/admin/feature/export-reservations/export-reservations.js"></script> <link rel="stylesheet" href="{{contextPath}}/resources/js/admin/feature/reservation/create/reservation-create.css" /> @@ -158,8 +162,8 @@ <li role="menuitem"><a data-ng-click="ctrl.openFieldSelectionModal()"><i class="fa fa-users"></i> attendees' data</a></li> <li role="menuitem"><a data-ng-click="ctrl.openWaitingQueueModal()" ><i class="fa fa-group"></i> waiting list</a></li> <li role="menuitem"><a data-ng-click="ctrl.downloadSponsorsScan()" ><i class="fa fa-barcode"></i> sponsors scan</a></li> - <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices()" ><i class="fa fa-file-o"></i> all Invoices (PDF)</a></li> - <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices('xls')" ><i class="fa fa-file-o"></i> all Invoices (Excel)</a></li> + <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices()" ><i class="fa fa-file-o"></i> all Billing Documents (PDF)</a></li> + <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices('xls')" ><i class="fa fa-file-o"></i> all Billing Documents (Excel)</a></li> </ul> <ul class="nav navbar-nav visible-sm visible-xs" ng-if="ctrl.eventName"> @@ -185,6 +189,9 @@ <li role="presentation" data-ui-sref-active="active"> <a data-ui-sref="events.single.reservationsList({eventName:ctrl.eventName})" ng-click="ctrl.menuCollapsed = true"><i class="fa fa-ticket"></i> Reservations</a> </li> + <li role="presentation" data-ui-sref-active="active" data-ng-if="!ctrl.event.free"> + <a data-ui-sref="events.single.paymentsList({eventName:ctrl.event.shortName})"><i class="fa fa-dollar"></i> Confirmed Payments</a> + </li> <li role="presentation" data-ui-sref-active="active"> <a data-ui-sref="events.single.email-log({eventName:ctrl.eventName})" ng-click="ctrl.menuCollapsed = true"><i class="fa fa-paper-plane-o"></i> E-mail log</a> </li> @@ -192,7 +199,7 @@ <ul class="nav navbar-nav visible-sm visible-xs"> <li class="nav-divider"></li> <li class="visible-xs visible-sm"> - <div class="pull-left"><a href="#" class="navbar-link" ng-click="ctrl.menuCollapsed = true" data-ui-sref="edit-current-user" title="click to update account details"><i class="fa fa-user"></i> {{username}}</a></div> + <div class="pull-left"><a href="#" class="navbar-link" ng-click="ctrl.menuCollapsed = true" data-ui-sref="edit-current-user" title="click to update account details"><i class="fa fa-user"></i> <span ng-non-bindable>{{username}}</span></a></div> <div class="pull-right"><a href="" class="navbar-link" data-ng-click="ctrl.doLogout('{{idpLogoutRedirectionUrl}}')"><i class="fa fa-sign-out"></i> Log out</a></div> </li> </ul> @@ -200,7 +207,7 @@ </div> <div class="navbar-right hidden-sm hidden-xs"> <ul class="nav navbar-nav"> - <li role="presentation" class="navbar-text"><i class="fa fa-user"></i> Logged in as {{username}}</li> + <li role="presentation" class="navbar-text"><i class="fa fa-user"></i> Logged in as <span ng-non-bindable>{{username}}</span></li> {{#isDBAuthentication}} <li role="presentation" data-ui-sref-active="active"> <a data-ui-sref="edit-current-user" title="click to update account details"><i class="fa fa-edit"></i> edit account</a></li> {{/isDBAuthentication}} diff --git a/src/main/webapp/resources/angular-templates/admin/partials/configuration/event.html b/src/main/webapp/resources/angular-templates/admin/partials/configuration/event.html index d89ea5f257..633a402023 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/configuration/event.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/configuration/event.html @@ -105,6 +105,9 @@ <h2>E-Mail</h2> <setting obj="eventConf.mail.enableHtmlEmails" data-display-delete-if-needed="true" data-delete-handler="eventConf.delete(config)"></setting> </div> </div> + <div> + <setting obj="eventConf.mail.mailFooter" data-display-delete-if-needed="true" data-delete-handler="eventConf.delete(config)"></setting> + </div> <div data-ng-repeat="setting in eventConf.mail.settings" data-ng-if="!eventConf.mail.type"> <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="eventConf.delete(config)"></setting> </div> diff --git a/src/main/webapp/resources/angular-templates/admin/partials/configuration/organization.html b/src/main/webapp/resources/angular-templates/admin/partials/configuration/organization.html index e91e28b1c4..2de51095bb 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/configuration/organization.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/configuration/organization.html @@ -103,6 +103,9 @@ <h2>E-Mail</h2> <div> <setting obj="organizationConf.mail.enableHtmlEmails" data-display-delete-if-needed="true" data-delete-handler="organizationConf.delete(config)"></setting> </div> + <div> + <setting obj="organizationConf.mail.mailFooter" data-display-delete-if-needed="true" data-delete-handler="organizationConf.delete(config)"></setting> + </div> </div> <div data-ng-repeat="setting in organizationConf.mail.settings" data-ng-if="!organizationConf.mail.type"> <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="organizationConf.delete(config)"></setting> diff --git a/src/main/webapp/resources/angular-templates/admin/partials/configuration/system.html b/src/main/webapp/resources/angular-templates/admin/partials/configuration/system.html index d4a187b80b..59b980007f 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/configuration/system.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/configuration/system.html @@ -34,6 +34,16 @@ <h2>Reservation Process</h2> </div> </div> + <div class="page-header" id="EMBED_PUBLIC_RESERVATION"> + <h2>Embedding options</h2> + <span>Embed public reservation process in an iFrame</span> + </div> + <div> + <div data-ng-repeat="setting in systemConf.reservationEmbed.settings"> + <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> + </div> + </div> + <div class="page-header" id="OPENID"> <h2>End-Users Authentication</h2> <span>End-Users authentication save profile</span> @@ -45,12 +55,28 @@ <h2>End-Users Authentication</h2> </div> <div class="page-header" id="PASS_INTEGRATION"> - <h2>Pass Integration</h2> - <span>Integration with Apple (tm) Pass</span> + <h2>Mobile wallet integration</h2> + <span>Integration with mobile Wallets</span> </div> - <div> - <div data-ng-repeat="setting in systemConf.passIntegration.settings"> - <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> + <div class="panel panel-default"> + <div class="panel-heading"> + <div class="panel-title">Google Wallet (tm) integration</div> + </div> + <div class="panel-body"> + <div data-ng-repeat="setting in systemConf.walletIntegration.settings"> + <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> + </div> + </div> + </div> + + <div class="panel panel-default"> + <div class="panel-heading"> + <div class="panel-title">Apple Pass (tm) integration</div> + </div> + <div class="panel-body"> + <div data-ng-repeat="setting in systemConf.passIntegration.settings"> + <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> + </div> </div> </div> @@ -113,6 +139,9 @@ <h2>E-Mail</h2> <div> <setting obj="systemConf.mail.enableHtmlEmails" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> </div> + <div> + <setting obj="systemConf.mail.mailFooter" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> + </div> </div> <div data-ng-repeat="setting in systemConf.mail.settings" data-ng-if="!systemConf.mail.type"> <setting data-obj="setting" data-display-delete-if-needed="true" data-delete-handler="systemConf.delete(config)"></setting> diff --git a/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html b/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html index 5e0b1dfe81..a6721c6f16 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html @@ -88,7 +88,7 @@ <h5 class="text-muted">Here the categories that have been defined for this event <i class="fa fa-envelope-o"></i> send invitations </a> <a class="btn btn-sm btn-default" data-ng-click="openConfiguration(event, ticketCategory)"><i class="fa fa-wrench"></i> options</a> - <a class="btn btn-sm btn-danger" ng-if="canBeDeleted(ticketCategory)" ng-click="deleteCategory(ticketCategory, event)"><i class="fa fa-trash"></i> delete</a> + <a class="btn btn-sm btn-danger" ng-if="canBeDeleted(event, ticketCategory)" ng-click="deleteCategory(ticketCategory, event)"><i class="fa fa-trash"></i> delete</a> </div> </div> </div> diff --git a/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/category-configuration-modal.html b/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/category-configuration-modal.html index fd2e37b557..bcfe198789 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/category-configuration-modal.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/category-configuration-modal.html @@ -1,4 +1,4 @@ <div class="modal-body"> - <ticket-category-configuration data-ng-if="ticketCategory.id !== undefined" data-category="ticketCategory" data-event="event" data-close-modal="$close('')"></ticket-category-configuration> + <ticket-category-configuration data-ng-if="ticketCategory.id !== undefined" data-category="ticketCategory" data-event="event" data-close-modal="$close('')" data-on-save="$close('')"></ticket-category-configuration> <div class="clearfix"></div> </div> \ No newline at end of file diff --git a/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html b/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html index e333d75001..5a6e46c99f 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html @@ -130,7 +130,7 @@ <h3>Logo</h3> <div class="form-group"> <label for="imageFile">Image</label> <div id="imageFile" class="drop-file-zone wMarginBottom well" data-accept="image/*" data-ngf-pattern="'image/*'" data-ng-model="droppedFile" data-ngf-drop data-ngf-select data-ngf-multiple="false" data-ngf-allow-dir="false" data-ngf-drag-over-class="'drop-file-zone-hover'"> - Drop image here or click to upload (Maximum size : 200KB) + Drop image here or click to upload (Maximum size : 1MB) </div> </div> </div> diff --git a/src/main/webapp/resources/angular-templates/admin/partials/main/sidebar.html b/src/main/webapp/resources/angular-templates/admin/partials/main/sidebar.html index ff356f3543..8ee7693a93 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/main/sidebar.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/main/sidebar.html @@ -21,8 +21,8 @@ <li role="menuitem"><a data-ng-click="ctrl.openFieldSelectionModal()"><i class="fa fa-users"></i> attendees' data</a></li> <li role="menuitem"><a data-ng-click="ctrl.openWaitingQueueModal()" ><i class="fa fa-group"></i> waiting list</a></li> <li role="menuitem"><a data-ng-click="ctrl.downloadSponsorsScan()" ><i class="fa fa-barcode"></i> sponsors scan</a></li> - <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices()" ><i class="fa fa-file-o"></i> all Invoices (PDF)</a></li> - <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices('xls')" ><i class="fa fa-file-o"></i> all Invoices (Excel)</a></li> + <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices()" ><i class="fa fa-file-o"></i> all Billing Documents (PDF)</a></li> + <li role="menuitem"><a data-ng-click="ctrl.downloadInvoices('xls')" ><i class="fa fa-file-o"></i> all Billing Documents (Excel)</a></li> </ul> </li> </ul> @@ -72,18 +72,21 @@ <h4 class="text-muted">Details</h4> <li role="presentation" data-ui-sref-active="active"> <a ui-sref="events.single.promoCodes({eventName: ctrl.event.shortName})"><i class="fa fa-percent"></i> {{ctrl.promoCodeDescription}} Codes</a> </li> - <li role="presentation" data-ui-sref-active="active"> + <li role="presentation" data-ui-sref-active="active" data-ng-if="!ctrl.freeOfCharge"> <a ui-sref="events.single.additionalServices({eventName: ctrl.event.shortName})"><i class="fa fa-money"></i> Additional options</a> </li> <li role="presentation" data-ui-sref-active="active"> <a ui-sref="events.single.polls-list({eventName: ctrl.event.shortName})"><i class="fa fa-bar-chart-o"></i> Polls</a> </li> - <li role="presentation" data-ui-sref-active="active"> + <li role="presentation" data-ui-sref-active="active" data-ng-if="!ctrl.freeOfCharge"> <a ui-sref="events.single.donations({eventName: ctrl.event.shortName})"><i class="fa fa-gift"></i> Donation options</a> </li> <li role="presentation" data-ui-sref-active="active"> <a data-ui-sref="events.single.reservationsList({eventName:ctrl.event.shortName})"><i class="fa fa-ticket"></i> Reservations</a> </li> + <li role="presentation" data-ui-sref-active="active" data-ng-if="!ctrl.freeOfCharge"> + <a data-ui-sref="events.single.paymentsList({eventName:ctrl.event.shortName})"><i class="fa fa-dollar"></i> Confirmed Payments</a> + </li> <li role="presentation" data-ui-sref-active="active"> <a data-ui-sref="events.single.email-log({eventName:ctrl.event.shortName})"><i class="fa fa-paper-plane-o"></i> E-mail log</a> </li> @@ -95,7 +98,7 @@ <h4 class="text-muted">Details</h4> <li role="presentation" ng-if="ctrl.event" data-ui-sref-active="active"> <waiting-queue-display-counter style-class="emulate-nav-pills-link" event-name="ctrl.event.shortName"></waiting-queue-display-counter> </li> - <li role="presentation" data-ng-if="ctrl.event" data-ui-sref-active="active"> + <li role="presentation" data-ng-if="ctrl.event && ctrl.offlineEnabled" data-ui-sref-active="active"> <pending-payments-link style-class="emulate-nav-pills-link" data-event="ctrl.event" data-event-name="ctrl.event.shortName"></pending-payments-link> </li> </ul> @@ -144,6 +147,6 @@ <h4 class="text-muted">Go to</h4> </section> <footer data-ng-if="ctrl.currentView !== 'UNKNOWN'"> <h5 class="text-muted">Powered by <a href="https://alf.io" target="_blank">Alf.io</a> v.{{ctrl.applicationInfo.version}}</h5> - <small><a href="https://github.com/alfio-event/alf.io/issues" target="_blank">report an issue</a> or <a href="https://groups.google.com/d/forum/alfio" target="_blank">ask for help</a></small> + <small><a href="https://github.com/alfio-event/alf.io/issues" target="_blank">report an issue</a> or <a href="https://github.com/alfio-event/alf.io/discussions" target="_blank" rel="nofollow noopener noreferrer">ask for help</a></small> </footer> </div> \ No newline at end of file diff --git a/src/main/webapp/resources/angular-templates/admin/partials/payment/edit-transaction-modal.html b/src/main/webapp/resources/angular-templates/admin/partials/payment/edit-transaction-modal.html new file mode 100644 index 0000000000..fada41cd0f --- /dev/null +++ b/src/main/webapp/resources/angular-templates/admin/partials/payment/edit-transaction-modal.html @@ -0,0 +1,24 @@ +<div class="modal-header"> + <h2 class="modal-title">{{$ctrl.actionText}} Payment for {{$ctrl.reservationId | limitTo:8 | uppercase}}</h2> +</div> +<div class="modal-body"> + <div class="form-group" ng-if="!$ctrl.isLoading"> + <label for="timestamp">Payment Received on (date/time)</label> + <input ng-if="$ctrl.transaction.timestampEditable" class="form-control" id="timestamp" name="timestamp" ng-model="$ctrl.transaction.timestampStr" single-date min-date="$ctrl.minDate" start-model="$ctrl.transaction.timestamp" required> + <div class="form-control-static" ng-if="!$ctrl.transaction.timestampEditable">{{$ctrl.transaction.timestamp.date}} {{$ctrl.transaction.timestamp.time}}</div> + </div> + <div class="form-group" ng-if="!$ctrl.isLoading"> + <label for="notes">Notes</label> + <textarea class="form-control" id="notes" name="notes" ng-model="$ctrl.transaction.notes"></textarea> + </div> +</div> +<div class="modal-footer"> + <div class="row"> + <div class="col-md-4 col-md-push-8 col-xs-12"> + <button class="btn btn-warning btn-lg btn-block btn-block-xs" type="button" data-ng-click="$ctrl.confirm()">{{$ctrl.confirmText}}</button> + </div> + <div class="col-md-4 col-md-pull-4 col-xs-12"> + <button class="btn btn-lg btn-default btn-block btn-block-xs" type="button" data-ng-click="$ctrl.cancel()">Cancel</button> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/main/webapp/resources/angular-templates/admin/partials/pending-payments/delete-or-credit-modal.html b/src/main/webapp/resources/angular-templates/admin/partials/pending-payments/delete-or-credit-modal.html index 9634df6f3c..9c9dc9bf41 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/pending-payments/delete-or-credit-modal.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/pending-payments/delete-or-credit-modal.html @@ -7,6 +7,15 @@ <h2 class="modal-title" ng-switch="$ctrl.credit"> <div class="modal-body" ng-switch="$ctrl.credit"> <p ng-switch-when="true">A new Billing Document of type <i>Credit Note</i> will be generated, and the reservation will be kept under the "Credit Note issued" tab of the reservations list</p> <p ng-switch-default="">Alf.io will discard the payment and delete the reservation for good.</p> + <div class="form-group"> + <div class="checkbox"> + <label> + <input type="checkbox" ng-model="$ctrl.notify"> + <span ng-if="$ctrl.credit">Send credit note to the reservation contact person</span> + <span ng-if="!$ctrl.credit">Send a notification email to the reservation contact person</span> + </label> + </div> + </div> </div> <div class="modal-footer"> <div class="row"> diff --git a/src/main/webapp/resources/css/admin.css b/src/main/webapp/resources/css/admin.css index 4f391da929..40d6a50a8a 100644 --- a/src/main/webapp/resources/css/admin.css +++ b/src/main/webapp/resources/css/admin.css @@ -838,4 +838,16 @@ span.path { .bg-white { background-color: white; +} + +.bg-gray { + background-color: #f9f9f9; +} + +table.table.transparent { + background-color: transparent; +} + +table.usage-details.tbody:not(:first-of-type) { + margin-top: 15px; } \ No newline at end of file diff --git a/src/main/webapp/resources/images/email/add-to-apple-wallet-button.png b/src/main/webapp/resources/images/email/add-to-apple-wallet-button.png new file mode 100644 index 0000000000..c42ba869a0 Binary files /dev/null and b/src/main/webapp/resources/images/email/add-to-apple-wallet-button.png differ diff --git a/src/main/webapp/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.png b/src/main/webapp/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.png new file mode 100644 index 0000000000..4854a1235f Binary files /dev/null and b/src/main/webapp/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.png differ diff --git a/src/main/webapp/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.svg b/src/main/webapp/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.svg new file mode 100644 index 0000000000..9fc8683719 --- /dev/null +++ b/src/main/webapp/resources/images/email/enUS_add_to_google_wallet_add-wallet-badge.svg @@ -0,0 +1,16 @@ +<svg width="199" height="55" viewBox="0 0 199 55" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="0.5" y="0.5" width="198" height="54" rx="27" fill="#1F1F1F"/> +<path d="M57 23.791H21V18.1456C21 15.0809 23.6422 12.5001 26.7798 12.5001H51.2202C54.3578 12.5001 57 15.0809 57 18.1456V23.791Z" fill="#34A853"/> +<path d="M57 29H21V23C21 19.7429 23.6422 17 26.7798 17H51.2202C54.3578 17 57 19.7429 57 23V29Z" fill="#FBBC04"/> +<path d="M57 34H21V28C21 24.7429 23.6422 22 26.7798 22H51.2202C54.3578 22 57 24.7429 57 28V34Z" fill="#EA4335"/> +<path d="M21 25.2409L43.8493 30.4025C46.4795 31.0477 49.4384 30.4025 51.5753 28.7895L57 24.9183V37.0157C57 40.0804 54.3699 42.4999 51.2466 42.4999H26.7534C23.6301 42.4999 21 40.0804 21 37.0157V25.2409Z" fill="url(#paint0_linear_1819_24225)"/> +<path d="M69.195 21.5L72.705 12.192H74.33L77.853 21.5H76.28L75.422 19.108H71.626L70.768 21.5H69.195ZM73.134 14.935L72.107 17.782H74.941L73.914 14.935L73.563 13.869H73.485L73.134 14.935ZM81.5143 21.708C80.9163 21.708 80.3747 21.5607 79.8893 21.266C79.4127 20.9627 79.0313 20.5467 78.7453 20.018C78.468 19.4807 78.3293 18.8697 78.3293 18.185C78.3293 17.5003 78.468 16.8937 78.7453 16.365C79.0313 15.8363 79.4127 15.4203 79.8893 15.117C80.3747 14.8137 80.9163 14.662 81.5143 14.662C82.0257 14.662 82.4633 14.7747 82.8273 15C83.2 15.2253 83.473 15.481 83.6463 15.767H83.7243L83.6463 14.844V12.192H85.0373V21.5H83.7243V20.616H83.6463C83.473 20.902 83.2 21.1577 82.8273 21.383C82.4633 21.5997 82.0257 21.708 81.5143 21.708ZM81.7223 20.421C82.069 20.421 82.394 20.33 82.6973 20.148C83.0093 19.966 83.2563 19.7103 83.4383 19.381C83.629 19.043 83.7243 18.6443 83.7243 18.185C83.7243 17.7257 83.629 17.3313 83.4383 17.002C83.2563 16.664 83.0093 16.404 82.6973 16.222C82.394 16.04 82.069 15.949 81.7223 15.949C81.3757 15.949 81.0507 16.04 80.7473 16.222C80.444 16.404 80.197 16.664 80.0063 17.002C79.8157 17.3313 79.7203 17.7257 79.7203 18.185C79.7203 18.6443 79.8157 19.043 80.0063 19.381C80.197 19.7103 80.444 19.966 80.7473 20.148C81.0507 20.33 81.3757 20.421 81.7223 20.421ZM89.4616 21.708C88.8636 21.708 88.3219 21.5607 87.8366 21.266C87.3599 20.9627 86.9786 20.5467 86.6926 20.018C86.4153 19.4807 86.2766 18.8697 86.2766 18.185C86.2766 17.5003 86.4153 16.8937 86.6926 16.365C86.9786 15.8363 87.3599 15.4203 87.8366 15.117C88.3219 14.8137 88.8636 14.662 89.4616 14.662C89.9729 14.662 90.4106 14.7747 90.7746 15C91.1473 15.2253 91.4203 15.481 91.5936 15.767H91.6716L91.5936 14.844V12.192H92.9846V21.5H91.6716V20.616H91.5936C91.4203 20.902 91.1473 21.1577 90.7746 21.383C90.4106 21.5997 89.9729 21.708 89.4616 21.708ZM89.6696 20.421C90.0163 20.421 90.3413 20.33 90.6446 20.148C90.9566 19.966 91.2036 19.7103 91.3856 19.381C91.5763 19.043 91.6716 18.6443 91.6716 18.185C91.6716 17.7257 91.5763 17.3313 91.3856 17.002C91.2036 16.664 90.9566 16.404 90.6446 16.222C90.3413 16.04 90.0163 15.949 89.6696 15.949C89.3229 15.949 88.9979 16.04 88.6946 16.222C88.3913 16.404 88.1443 16.664 87.9536 17.002C87.7629 17.3313 87.6676 17.7257 87.6676 18.185C87.6676 18.6443 87.7629 19.043 87.9536 19.381C88.1443 19.7103 88.3913 19.966 88.6946 20.148C88.9979 20.33 89.3229 20.421 89.6696 20.421ZM98.3745 19.576V16.092H97.2175V14.87H98.3745V12.998H99.7785V14.87H101.404V16.092H99.7785V19.277C99.7785 19.6063 99.8435 19.8577 99.9735 20.031C100.112 20.2043 100.342 20.291 100.663 20.291C100.827 20.291 100.966 20.2693 101.079 20.226C101.2 20.1827 101.321 20.122 101.443 20.044V21.409C101.295 21.4697 101.139 21.5173 100.975 21.552C100.81 21.5867 100.615 21.604 100.39 21.604C99.7742 21.604 99.2845 21.4263 98.9205 21.071C98.5565 20.707 98.3745 20.2087 98.3745 19.576ZM105.617 21.708C104.932 21.708 104.33 21.552 103.81 21.24C103.29 20.928 102.882 20.5077 102.588 19.979C102.293 19.4503 102.146 18.8523 102.146 18.185C102.146 17.5263 102.293 16.9327 102.588 16.404C102.882 15.8667 103.29 15.442 103.81 15.13C104.33 14.818 104.932 14.662 105.617 14.662C106.293 14.662 106.891 14.818 107.411 15.13C107.931 15.442 108.338 15.8667 108.633 16.404C108.927 16.9327 109.075 17.5263 109.075 18.185C109.075 18.8523 108.927 19.4503 108.633 19.979C108.338 20.5077 107.931 20.928 107.411 21.24C106.891 21.552 106.293 21.708 105.617 21.708ZM105.617 20.421C105.981 20.421 106.319 20.3343 106.631 20.161C106.943 19.979 107.194 19.7233 107.385 19.394C107.584 19.056 107.684 18.653 107.684 18.185C107.684 17.717 107.584 17.3183 107.385 16.989C107.194 16.651 106.943 16.3953 106.631 16.222C106.319 16.04 105.981 15.949 105.617 15.949C105.253 15.949 104.91 16.04 104.59 16.222C104.278 16.3953 104.022 16.651 103.823 16.989C103.632 17.3183 103.537 17.717 103.537 18.185C103.537 18.653 103.632 19.056 103.823 19.394C104.022 19.7233 104.278 19.979 104.59 20.161C104.91 20.3343 105.253 20.421 105.617 20.421Z" fill="white"/> +<path d="M76.14 41.772C75.2673 41.772 74.4457 41.6133 73.675 41.296C72.9157 40.9787 72.2413 40.5367 71.652 39.97C71.0627 39.392 70.598 38.7177 70.258 37.947C69.9293 37.165 69.765 36.3207 69.765 35.414C69.765 34.5073 69.9293 33.6687 70.258 32.898C70.598 32.116 71.057 31.4417 71.635 30.875C72.2243 30.297 72.9043 29.8493 73.675 29.532C74.4457 29.2147 75.2673 29.056 76.14 29.056C77.0693 29.056 77.925 29.2203 78.707 29.549C79.5003 29.8777 80.1633 30.3367 80.696 30.926L79.404 32.201C79.0073 31.759 78.5313 31.419 77.976 31.181C77.432 30.943 76.82 30.824 76.14 30.824C75.3353 30.824 74.593 31.0167 73.913 31.402C73.233 31.776 72.6833 32.3087 72.264 33C71.856 33.68 71.652 34.4847 71.652 35.414C71.652 36.3433 71.8617 37.1537 72.281 37.845C72.7003 38.525 73.25 39.0577 73.93 39.443C74.61 39.817 75.3523 40.004 76.157 40.004C76.8937 40.004 77.5623 39.868 78.163 39.596C78.7637 39.3127 79.2453 38.916 79.608 38.406C79.982 37.896 80.2087 37.284 80.288 36.57H76.123V34.921H82.039C82.107 35.227 82.141 35.55 82.141 35.89V35.907C82.141 37.0857 81.8803 38.117 81.359 39.001C80.849 39.8737 80.1407 40.5537 79.234 41.041C78.3273 41.5283 77.296 41.772 76.14 41.772ZM87.6149 41.772C86.7195 41.772 85.9319 41.568 85.2519 41.16C84.5719 40.752 84.0392 40.2023 83.6539 39.511C83.2685 38.8197 83.0759 38.0377 83.0759 37.165C83.0759 36.3037 83.2685 35.5273 83.6539 34.836C84.0392 34.1333 84.5719 33.578 85.2519 33.17C85.9319 32.762 86.7195 32.558 87.6149 32.558C88.4989 32.558 89.2809 32.762 89.9609 33.17C90.6409 33.578 91.1735 34.1333 91.5589 34.836C91.9442 35.5273 92.1369 36.3037 92.1369 37.165C92.1369 38.0377 91.9442 38.8197 91.5589 39.511C91.1735 40.2023 90.6409 40.752 89.9609 41.16C89.2809 41.568 88.4989 41.772 87.6149 41.772ZM87.6149 40.089C88.0909 40.089 88.5329 39.9757 88.9409 39.749C89.3489 39.511 89.6775 39.1767 89.9269 38.746C90.1875 38.304 90.3179 37.777 90.3179 37.165C90.3179 36.553 90.1875 36.0317 89.9269 35.601C89.6775 35.159 89.3489 34.8247 88.9409 34.598C88.5329 34.36 88.0909 34.241 87.6149 34.241C87.1389 34.241 86.6912 34.36 86.2719 34.598C85.8639 34.8247 85.5295 35.159 85.2689 35.601C85.0195 36.0317 84.8949 36.553 84.8949 37.165C84.8949 37.777 85.0195 38.304 85.2689 38.746C85.5295 39.1767 85.8639 39.511 86.2719 39.749C86.6912 39.9757 87.1389 40.089 87.6149 40.089ZM97.9078 41.772C97.0125 41.772 96.2248 41.568 95.5448 41.16C94.8648 40.752 94.3322 40.2023 93.9468 39.511C93.5615 38.8197 93.3688 38.0377 93.3688 37.165C93.3688 36.3037 93.5615 35.5273 93.9468 34.836C94.3322 34.1333 94.8648 33.578 95.5448 33.17C96.2248 32.762 97.0125 32.558 97.9078 32.558C98.7918 32.558 99.5738 32.762 100.254 33.17C100.934 33.578 101.467 34.1333 101.852 34.836C102.237 35.5273 102.43 36.3037 102.43 37.165C102.43 38.0377 102.237 38.8197 101.852 39.511C101.467 40.2023 100.934 40.752 100.254 41.16C99.5738 41.568 98.7918 41.772 97.9078 41.772ZM97.9078 40.089C98.3838 40.089 98.8258 39.9757 99.2338 39.749C99.6418 39.511 99.9705 39.1767 100.22 38.746C100.481 38.304 100.611 37.777 100.611 37.165C100.611 36.553 100.481 36.0317 100.22 35.601C99.9705 35.159 99.6418 34.8247 99.2338 34.598C98.8258 34.36 98.3838 34.241 97.9078 34.241C97.4318 34.241 96.9842 34.36 96.5648 34.598C96.1568 34.8247 95.8225 35.159 95.5618 35.601C95.3125 36.0317 95.1878 36.553 95.1878 37.165C95.1878 37.777 95.3125 38.304 95.5618 38.746C95.8225 39.1767 96.1568 39.511 96.5648 39.749C96.9842 39.9757 97.4318 40.089 97.9078 40.089ZM108.031 45.444C107.271 45.444 106.614 45.3193 106.059 45.07C105.515 44.832 105.073 44.5203 104.733 44.135C104.393 43.761 104.149 43.3757 104.002 42.979L105.702 42.265C105.883 42.7183 106.172 43.0867 106.569 43.37C106.977 43.6647 107.464 43.812 108.031 43.812C108.824 43.812 109.447 43.574 109.901 43.098C110.365 42.622 110.598 41.9477 110.598 41.075V40.242H110.496C110.224 40.65 109.844 40.9787 109.357 41.228C108.881 41.466 108.337 41.585 107.725 41.585C106.988 41.585 106.314 41.398 105.702 41.024C105.09 40.65 104.597 40.1287 104.223 39.46C103.849 38.78 103.662 37.9867 103.662 37.08C103.662 36.162 103.849 35.3687 104.223 34.7C104.597 34.02 105.09 33.493 105.702 33.119C106.314 32.745 106.988 32.558 107.725 32.558C108.337 32.558 108.881 32.6827 109.357 32.932C109.844 33.1813 110.224 33.51 110.496 33.918H110.598V32.83H112.349V41.041C112.349 41.9817 112.162 42.7807 111.788 43.438C111.425 44.0953 110.921 44.594 110.275 44.934C109.629 45.274 108.881 45.444 108.031 45.444ZM108.048 39.919C108.501 39.919 108.92 39.8113 109.306 39.596C109.691 39.3693 110.003 39.0463 110.241 38.627C110.479 38.1963 110.598 37.6807 110.598 37.08C110.598 36.4567 110.479 35.9353 110.241 35.516C110.003 35.0853 109.691 34.7623 109.306 34.547C108.92 34.3317 108.501 34.224 108.048 34.224C107.594 34.224 107.169 34.3373 106.773 34.564C106.387 34.7793 106.076 35.0967 105.838 35.516C105.6 35.9353 105.481 36.4567 105.481 37.08C105.481 37.692 105.6 38.2133 105.838 38.644C106.076 39.0633 106.387 39.3807 106.773 39.596C107.169 39.8113 107.594 39.919 108.048 39.919ZM114.514 41.5V29.328H116.35V41.5H114.514ZM122.392 41.772C121.542 41.772 120.783 41.5737 120.114 41.177C119.446 40.7803 118.919 40.2363 118.533 39.545C118.159 38.8537 117.972 38.066 117.972 37.182C117.972 36.3547 118.154 35.5897 118.516 34.887C118.879 34.1843 119.383 33.6233 120.029 33.204C120.687 32.7733 121.44 32.558 122.29 32.558C123.186 32.558 123.945 32.7507 124.568 33.136C125.203 33.5213 125.685 34.0483 126.013 34.717C126.342 35.3857 126.506 36.1393 126.506 36.978C126.506 37.1027 126.501 37.216 126.489 37.318C126.489 37.42 126.484 37.4993 126.472 37.556H119.774C119.865 38.4173 120.165 39.0633 120.675 39.494C121.197 39.9247 121.786 40.14 122.443 40.14C123.033 40.14 123.52 40.0097 123.905 39.749C124.291 39.477 124.597 39.1427 124.823 38.746L126.336 39.477C125.962 40.157 125.452 40.7123 124.806 41.143C124.16 41.5623 123.356 41.772 122.392 41.772ZM122.307 34.122C121.695 34.122 121.174 34.309 120.743 34.683C120.313 35.057 120.024 35.5557 119.876 36.179H124.687C124.665 35.8843 124.568 35.5783 124.398 35.261C124.228 34.9437 123.968 34.6773 123.616 34.462C123.276 34.2353 122.84 34.122 122.307 34.122ZM133.979 41.5L130.715 29.328H132.789L134.727 37.233L134.931 38.287H135.033L135.288 37.233L137.736 29.328H139.606L141.952 37.233L142.207 38.27H142.309L142.513 37.233L144.451 29.328H146.508L143.295 41.5H141.323L138.994 33.425L138.722 32.286H138.62L138.331 33.425L135.9 41.5H133.979ZM149.922 41.772C149.299 41.772 148.743 41.653 148.256 41.415C147.769 41.1657 147.389 40.82 147.117 40.378C146.845 39.936 146.709 39.4317 146.709 38.865C146.709 38.253 146.868 37.7317 147.185 37.301C147.514 36.859 147.95 36.5247 148.494 36.298C149.038 36.0713 149.639 35.958 150.296 35.958C150.84 35.958 151.316 36.009 151.724 36.111C152.143 36.213 152.461 36.3207 152.676 36.434V35.975C152.676 35.4083 152.472 34.955 152.064 34.615C151.656 34.275 151.129 34.105 150.483 34.105C150.041 34.105 149.622 34.207 149.225 34.411C148.828 34.6037 148.511 34.87 148.273 35.21L147.015 34.241C147.389 33.7197 147.882 33.3117 148.494 33.017C149.117 32.711 149.797 32.558 150.534 32.558C151.792 32.558 152.761 32.8697 153.441 33.493C154.121 34.105 154.461 34.9663 154.461 36.077V41.5H152.676V40.429H152.574C152.347 40.7803 152.007 41.092 151.554 41.364C151.101 41.636 150.557 41.772 149.922 41.772ZM150.245 40.276C150.721 40.276 151.14 40.1627 151.503 39.936C151.866 39.7093 152.149 39.4147 152.353 39.052C152.568 38.678 152.676 38.2757 152.676 37.845C152.415 37.6977 152.109 37.5787 151.758 37.488C151.407 37.386 151.033 37.335 150.636 37.335C149.888 37.335 149.355 37.488 149.038 37.794C148.721 38.0887 148.562 38.4513 148.562 38.882C148.562 39.29 148.715 39.6243 149.021 39.885C149.327 40.1457 149.735 40.276 150.245 40.276ZM156.499 41.5V29.328H158.335V41.5H156.499ZM160.517 41.5V29.328H162.353V41.5H160.517ZM168.395 41.772C167.545 41.772 166.786 41.5737 166.117 41.177C165.449 40.7803 164.922 40.2363 164.536 39.545C164.162 38.8537 163.975 38.066 163.975 37.182C163.975 36.3547 164.157 35.5897 164.519 34.887C164.882 34.1843 165.386 33.6233 166.032 33.204C166.69 32.7733 167.443 32.558 168.293 32.558C169.189 32.558 169.948 32.7507 170.571 33.136C171.206 33.5213 171.688 34.0483 172.016 34.717C172.345 35.3857 172.509 36.1393 172.509 36.978C172.509 37.1027 172.504 37.216 172.492 37.318C172.492 37.42 172.487 37.4993 172.475 37.556H165.777C165.868 38.4173 166.168 39.0633 166.678 39.494C167.2 39.9247 167.789 40.14 168.446 40.14C169.036 40.14 169.523 40.0097 169.908 39.749C170.294 39.477 170.6 39.1427 170.826 38.746L172.339 39.477C171.965 40.157 171.455 40.7123 170.809 41.143C170.163 41.5623 169.359 41.772 168.395 41.772ZM168.31 34.122C167.698 34.122 167.177 34.309 166.746 34.683C166.316 35.057 166.027 35.5557 165.879 36.179H170.69C170.668 35.8843 170.571 35.5783 170.401 35.261C170.231 34.9437 169.971 34.6773 169.619 34.462C169.279 34.2353 168.843 34.122 168.31 34.122ZM174.882 38.984V34.428H173.369V32.83H174.882V30.382H176.718V32.83H178.843V34.428H176.718V38.593C176.718 39.0237 176.803 39.3523 176.973 39.579C177.154 39.8057 177.454 39.919 177.874 39.919C178.089 39.919 178.27 39.8907 178.418 39.834C178.576 39.7773 178.735 39.698 178.894 39.596V41.381C178.701 41.4603 178.497 41.5227 178.282 41.568C178.066 41.6133 177.811 41.636 177.517 41.636C176.712 41.636 176.072 41.4037 175.596 40.939C175.12 40.463 174.882 39.8113 174.882 38.984Z" fill="white"/> +<rect x="0.5" y="0.5" width="198" height="54" rx="27" stroke="#747775"/> +<defs> +<linearGradient id="paint0_linear_1819_24225" x1="37.2843" y1="34.0448" x2="18.7823" y2="55.7227" gradientUnits="userSpaceOnUse"> +<stop stop-color="#4285F4"/> +<stop offset="1" stop-color="#1B74E8"/> +</linearGradient> +</defs> +</svg> diff --git a/src/main/webapp/resources/js/admin/directive/admin-directive.js b/src/main/webapp/resources/js/admin/directive/admin-directive.js index 8d5dc154e3..e79448a8a7 100644 --- a/src/main/webapp/resources/js/admin/directive/admin-directive.js +++ b/src/main/webapp/resources/js/admin/directive/admin-directive.js @@ -884,12 +884,14 @@ $rootScope.$broadcast('PendingReservationsFound', count); }); }; - getPendingPayments(); - var promise = $interval(getPendingPayments, 10000); + if (window.USER_IS_OWNER) { + getPendingPayments(); + var promise = $interval(getPendingPayments, 10000); - scope.$on('$destroy', function() { - $interval.cancel(promise); - }); + scope.$on('$destroy', function() { + $interval.cancel(promise); + }); + } } else { var listener = $rootScope.$on('PendingReservationsFound', function(data) { scope.pendingReservationsCount = data; @@ -1149,6 +1151,8 @@ }); ctrl.event = event.event; ctrl.internal = true; + ctrl.freeOfCharge = ctrl.event.free; + ctrl.offlineEnabled = !ctrl.freeOfCharge && ctrl.event.allowedPaymentProxies.includes('OFFLINE'); ctrl.owner = ctrl.event.visibleForCurrentUser; ctrl.openDeleteWarning = function() { EventService.deleteEvent(ctrl.event).then(function(result) { diff --git a/src/main/webapp/resources/js/admin/feature/additional-service/additional-service.js b/src/main/webapp/resources/js/admin/feature/additional-service/additional-service.js index cfa8f9dda5..ad63e9eab7 100644 --- a/src/main/webapp/resources/js/admin/feature/additional-service/additional-service.js +++ b/src/main/webapp/resources/js/admin/feature/additional-service/additional-service.js @@ -96,14 +96,13 @@ }); self.displayList = buildDisplayList(self.list); self.additionalServiceUseCount = results[2].data; - var countSold = 0; + var countStatus = 0; self.displayList.map(function(i) { - if (self.additionalServiceUseCount[i.id] > 0) { - countSold ++; + if (Object.values(self.additionalServiceUseCount[i.id] || {}).some((v) => v > 0)) { + countStatus++; } }); - self.allowDownload = countSold > 0; - + self.allowDownload = countStatus > 0; }); function fillExistingTexts(texts) { @@ -117,6 +116,36 @@ return _.zip(item.title, item.description); }; + self.getSold = function(additionalServiceId) { + var statusCount = self.additionalServiceUseCount[additionalServiceId] || {}; + var acquired = statusCount['ACQUIRED'] || 0; + var checkedIn = statusCount['CHECKED_IN'] || 0; + var toBePaid = statusCount['TO_BE_PAID'] || 0; + return [ + {c : acquired, s: 'Acquired' }, + {c: checkedIn, s: 'Checked in'}, + {c: toBePaid, s: 'To be paid on site'} + ]; + }; + + self.countSold = function(additionalServiceId) { + var soldCount = self.getSold(additionalServiceId); + return soldCount.reduce(function(acc, cur) {return acc + cur.c}, 0); + }; + + self.formatCountSold = function(additionalServiceId) { + var composition = self.getSold(additionalServiceId).filter(function(v) {return v.c > 0}) + var showBreakDown = composition.length > 1; + var total = self.countSold(additionalServiceId); + var afterText = ''; + if (showBreakDown) { + afterText = ' (of which '; + afterText += composition.map((function(v) { return v.s + ': ' + v.c})).join(', '); + afterText += ')'; + } + return total + afterText; + }; + self.addedItem = undefined; self.edit = function(item) { diff --git a/src/main/webapp/resources/js/admin/feature/additional-service/additional-services.html b/src/main/webapp/resources/js/admin/feature/additional-service/additional-services.html index bf6506552b..35a2df2274 100644 --- a/src/main/webapp/resources/js/admin/feature/additional-service/additional-services.html +++ b/src/main/webapp/resources/js/admin/feature/additional-service/additional-services.html @@ -15,7 +15,7 @@ <h3> <div class="col-xs-9"> <div class="h4"> <span data-ng-repeat="pair in item.zippedTitleAndDescriptions" title="{{pair[0].locale}}"><span ng-class="{'text-danger': pair[0].value === ''}">{{pair[0] | showMissingASText}}</span> <span data-ng-if="!$last"> / </span></span> - <span style="margin-left: 20px;" class="text-success" data-ng-if="ctrl.additionalServiceUseCount[item.id] > 0"> Confirmed: {{ctrl.additionalServiceUseCount[item.id]}}</span> + <span style="margin-left: 20px;" class="text-success" data-ng-if="ctrl.countSold(item.id) > 0"> Confirmed: {{ctrl.formatCountSold(item.id)}}</span> </div> </div> <div class="col-xs-3"> diff --git a/src/main/webapp/resources/js/admin/feature/apikey-bulk-import/apikey-bulk-import.js b/src/main/webapp/resources/js/admin/feature/apikey-bulk-import/apikey-bulk-import.js index 8edf83117d..7782e7cb17 100644 --- a/src/main/webapp/resources/js/admin/feature/apikey-bulk-import/apikey-bulk-import.js +++ b/src/main/webapp/resources/js/admin/feature/apikey-bulk-import/apikey-bulk-import.js @@ -27,7 +27,7 @@ $q.all([OrganizationService.getAllOrganizations(), UserService.getAllRoles()]).then(function(results) { ctrl.organizations = results[0].data; - ctrl.roles = _.filter(results[1].data, function(r) { return r.target === 'API_KEY'; }); + ctrl.roles = _.filter(results[1].data, function(r) { return r.target.indexOf('API_KEY') > -1; }); }); }; diff --git a/src/main/webapp/resources/js/admin/feature/configuration/configuration.js b/src/main/webapp/resources/js/admin/feature/configuration/configuration.js index 747cf57751..fdfc836cb5 100644 --- a/src/main/webapp/resources/js/admin/feature/configuration/configuration.js +++ b/src/main/webapp/resources/js/admin/feature/configuration/configuration.js @@ -100,9 +100,15 @@ return $http.get('/admin/api/configuration/events/'+eventId+'/load').error(HttpErrorHandler.handle) }, loadSingleConfigForEvent: function(eventId, key) { + if (!window.USER_IS_OWNER) { + return $q.reject('not authorized'); + } return $http.get('/admin/api/configuration/events/'+eventId+'/single/'+key).error(HttpErrorHandler.handle) }, loadSingleConfigForOrganization: function(organizationId, key) { + if (!window.USER_IS_OWNER) { + return $q.reject('not authorized'); + } return $http.get('/admin/api/configuration/organizations/'+organizationId+'/single/'+key).error(HttpErrorHandler.handle) }, loadInstanceSettings: function() { @@ -159,7 +165,8 @@ cc: _.find(original['MAIL'], function(e) {return e.configurationKey === 'MAIL_SYSTEM_NOTIFICATION_CC';}), mailReplyTo: _.find(original['MAIL'], function(e) {return e.configurationKey === 'MAIL_REPLY_TO';}), mailAttemptsCount: _.find(original['MAIL'], function(e) {return e.configurationKey === 'MAIL_ATTEMPTS_COUNT';}), - enableHtmlEmails: _.find(original['MAIL'], function(e) {return e.configurationKey === 'ENABLE_HTML_EMAILS';}) + enableHtmlEmails: _.find(original['MAIL'], function(e) {return e.configurationKey === 'ENABLE_HTML_EMAILS';}), + mailFooter: _.find(original['MAIL'], function(e) {return e.configurationKey === 'MAIL_FOOTER';}) }; } @@ -653,7 +660,8 @@ scope: { event: '=', category: '=', - closeModal: '&' + closeModal: '&', + onSave: '&' }, bindToController: true, controller: ['ConfigurationService', '$rootScope', 'GroupService', '$q', CategoryConfigurationController], @@ -693,14 +701,15 @@ return; } categoryConf.loading = true; + var onSaveComplete = categoryConf.onSave ? categoryConf.onSave : load; ConfigurationService.updateCategoryConfig(categoryConf.category.id, categoryConf.event.id, categoryConf.settings).then(function() { if(categoryConf.group) { GroupService.linkTo(categoryConf.group).then(function() { - load(); + onSaveComplete(); }); } else { - load(); + onSaveComplete(); } }, function(e) { alert(e.data); diff --git a/src/main/webapp/resources/js/admin/feature/configuration/regenerate-invoices.html b/src/main/webapp/resources/js/admin/feature/configuration/regenerate-invoices.html index e68be8ab6c..c9f0b252f8 100644 --- a/src/main/webapp/resources/js/admin/feature/configuration/regenerate-invoices.html +++ b/src/main/webapp/resources/js/admin/feature/configuration/regenerate-invoices.html @@ -6,7 +6,7 @@ <div class="row"> <div class="col-xs-12 text-left"> Invoices are generated as PDFs and remain persistent based on the original event settings. If you make changes to your event settings and need this reflected on previously issued invoices, you can regenerate the invoice PDFs via this tool.<br><br> - <b>Note</b> that once changed, the changes will be reflected across all invoices generated between the date range chosen.<br> + <b>Note</b> that once changed, the changes will be reflected across all invoices generated between the chosen date range.<br> If you need to change the info of a single invoice to reflect a change in contact details, you should regenerate the invoice in the relevant reservation record only. </div> </div> diff --git a/src/main/webapp/resources/js/admin/feature/event-data-to-collect/event-data-to-collect.html b/src/main/webapp/resources/js/admin/feature/event-data-to-collect/event-data-to-collect.html index 7335cd6e68..5901438e2f 100644 --- a/src/main/webapp/resources/js/admin/feature/event-data-to-collect/event-data-to-collect.html +++ b/src/main/webapp/resources/js/admin/feature/event-data-to-collect/event-data-to-collect.html @@ -68,7 +68,7 @@ <h5 class="text-muted">The following data will be collected (full name, e-mail a </div> </div> </div> - <hr data-ng-if="field.type == 'select'" /> + <hr data-ng-if="field.type == 'select' || field.type == 'country'" /> <div class="row" data-ng-if="field.type == 'select'"> <div class="col-xs-1"><strong>Values</strong></div> <div class="col-xs-10"> @@ -81,6 +81,9 @@ <h5 class="text-muted">The following data will be collected (full name, e-mail a </div> <div class="col-xs-1"><button class="btn btn-default btn-xs" type="button" ng-click="$ctrl.openStats(field)"><i class="fa fa-bar-chart-o"></i> Statistics</button></div> </div> + <div class="row" data-ng-if="field.type == 'country'"> + <div class="col-xs-1 col-xs-offset-11"><button class="btn btn-default btn-xs" type="button" ng-click="$ctrl.openStats(field)"><i class="fa fa-bar-chart-o"></i> Statistics</button></div> + </div> <hr data-ng-if="field.context === 'ADDITIONAL_SERVICE'" /> <div data-ng-if="field.context === 'ADDITIONAL_SERVICE'"> diff --git a/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations-button.html b/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations-button.html new file mode 100644 index 0000000000..edcd153d23 --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations-button.html @@ -0,0 +1,5 @@ +<div class="row wMarginBottom30px" data-ng-if="$ctrl.eventsCount > 0"> + <div class="col-xs-6 col-lg-3 col-xs-push-6 col-lg-push-9"> + <button type="button" class="btn btn-block" ng-click="$ctrl.openModal()"><em class="fa fa-download"></em> Export Reservations</button> + </div> +</div> \ No newline at end of file diff --git a/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations-form.html b/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations-form.html new file mode 100644 index 0000000000..53cf286bc3 --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations-form.html @@ -0,0 +1,43 @@ +<div class="modal-content"> + <div class="modal-header"> + <h2>Export Reservations</h2> + </div> + <div class="modal-body"> + + <div class="row"> + <div class="col-xs-12 text-left"> + Reservations can be exported for a given period of time.<br> + The export will produce an "Excel" file with one sheet per Event + </div> + </div> + <hr> + <h4>Reservation date</h4> + <div class="row"> + <div class="col-xs-12 col-lg-6"> + <div class="form-group"> + <label for="reservation-date-from">From</label> + <input type="date" autocomplete="off" id="reservation-date-from" ng-model="$ctrl.searchFrom" class="form-control"> + </div> + </div> + <div class="col-xs-12 col-lg-6"> + <div class="form-group"> + <label for="reservation-date-to">To</label> + <input type="date" autocomplete="off" id="reservation-date-to" ng-model="$ctrl.searchTo" class="form-control"> + </div> + </div> + </div> + </div> + <div class="modal-footer"> + <div class="row"> + <div class="col-md-4 col-xs-12"> + <button class="btn btn-lg btn-default btn-block" style="margin-bottom: 10px" ng-click="$ctrl.close()">Close</button> + </div> + <div class="col-md-4 col-md-push-4 col-xs-12"> + <a class="btn btn-lg btn-warning btn-block" style="margin-bottom: 10px" + href="{{'/admin/api/export/reservations?from=' + $ctrl.formattedSearchFrom + '&to=' + $ctrl.formattedSearchTo }}" + data-ng-if="$ctrl.ready" ng-click="$ctrl.download()" + target="_blank" rel="noopener">Download</a> + </div> + </div> + </div> +</div> diff --git a/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations.js b/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations.js new file mode 100644 index 0000000000..74273d3ced --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/export-reservations/export-reservations.js @@ -0,0 +1,60 @@ +(function() { + +'use strict'; + + +angular.module('adminApplication').component('exportReservationsButton', { + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/export-reservations/export-reservations-button.html', + controller: exportReservationsButton +}); + + +function exportReservationsButton(EventService, $uibModal) { + var ctrl = this; + + ctrl.$onInit = function() { + EventService.getEventsCount().then(function(response) { + ctrl.eventsCount = response.data; + }); + } + + ctrl.openModal = function() { + $uibModal.open({ + size: 'lg', + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/export-reservations/export-reservations-form.html', + backdrop: 'static', + controllerAs: '$ctrl', + bindToController: true, + controller: function($scope) { + var modalCtrl = this; + modalCtrl.maxDate = moment().endOf('day'); + modalCtrl.searchFrom = null; + modalCtrl.searchTo = null; + modalCtrl.ready = false; + modalCtrl.formattedSearchFrom = null; + modalCtrl.formattedSearchTo = null; + + modalCtrl.close = function() { + $scope.$dismiss(); + } + + $scope.$watch('$ctrl.searchFrom', function(newValue) { + if(newValue) { + modalCtrl.ready = moment(modalCtrl.searchTo).isAfter(moment(newValue)); + modalCtrl.formattedSearchFrom = moment(newValue).format('YYYY-MM-DD'); + } + }); + $scope.$watch('$ctrl.searchTo', function(newValue) { + if(newValue) { + modalCtrl.ready = moment(newValue).isAfter(moment(modalCtrl.searchFrom)); + modalCtrl.formattedSearchTo = moment(newValue).format('YYYY-MM-DD'); + } + }); + } + }); + } +} + +exportReservationsButton.$inject = ['EventService', '$uibModal']; + +})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/admin/feature/group/groups.js b/src/main/webapp/resources/js/admin/feature/group/groups.js index d602bbf0cb..45abcc1a9d 100644 --- a/src/main/webapp/resources/js/admin/feature/group/groups.js +++ b/src/main/webapp/resources/js/admin/feature/group/groups.js @@ -94,7 +94,9 @@ ctrl.$onInit = function() { ctrl.groups = []; - loadGroups(); + if (window.USER_IS_OWNER) { + loadGroups(); + } }; function loadGroups() { @@ -231,6 +233,9 @@ function GroupService($http, HttpErrorHandler, $q, NotificationHandler) { return { loadGroups: function(orgId) { + if (!window.USER_IS_OWNER) { + return $q.reject('not authorized'); + } return $http.get('/admin/api/group/for/'+orgId).error(HttpErrorHandler.handle); }, loadGroupDetail: function(orgId, groupId) { diff --git a/src/main/webapp/resources/js/admin/feature/payments-list/payments-list.html b/src/main/webapp/resources/js/admin/feature/payments-list/payments-list.html new file mode 100644 index 0000000000..6554d778f7 --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/payments-list/payments-list.html @@ -0,0 +1,59 @@ +<div data-ng-class="{'container': $ctrl.purchaseContextType === 'event'}"> + <div class="page-header"> + <h2>Confirmed Payments for <i>{{$ctrl.purchaseContextTitle}}</i></h2> + </div> + + <div class="row"> + <div class="col-xs-10"> + <div class="form-group"> + <label class="sr-only" for="filter-reservations">Filter Payments</label> + <div class="input-group"> + <div class="input-group-addon"> + <i class="fa fa-search"></i> + </div> + <input type="text" class="form-control" id="filter-reservations" ng-model="$ctrl.toSearch" ng-change="$ctrl.updateFilteredData()" ng-model-options='{ debounce: 1000 }' placeholder="Filter Payments"> + <div class="input-group-btn" ng-if="$ctrl.toSearch.length > 0"> + <button type="button" class="btn btn-default" ng-click="$ctrl.toSearch = ''; $ctrl.updateFilteredData()">Reset</button> + </div> + </div> + </div> + </div> + <div class="col-xs-2" ng-if="$ctrl.foundReservations > 0"> + <a class="btn btn-block btn-default" ng-href="/admin/api/payments/{{$ctrl.purchaseContext.type}}/{{$ctrl.purchaseContext.publicIdentifier}}/download?search={{$ctrl.toSearch}}" target="_blank" rel="noopener"><i class="fa fa-download"></i> Export</a> + </div> + </div> + + <div class="alert alert-info text-center wMarginTop10px" ng-if="$ctrl.foundReservations == 0"><i class="fa fa-info-circle"></i> No payments found</div> + <div class="table-responsive" ng-if="$ctrl.foundReservations > 0"> + <table class="table table-striped"> + <thead> + <tr> + <th width="5%">Id</th> + <th width="15%">Name</th> + <th width="15%">Email</th> + <th width="10%">Payment</th> + <th width="10%">Amount</th> + <th width="15%">Date</th> + <th width="25%">Notes</th> + <th width="5%"> </th> + </tr> + </thead> + <tbody> + <tr ng-repeat="r in $ctrl.reservations"> + <td data-ng-if="$ctrl.targetContextType === 'event'"><a ui-sref="events.single.view-reservation({ eventName : (r.eventPublicIdentifier || $ctrl.purchaseContext.publicIdentifier), reservationId: r.id})" target="_blank"><reservation-identifier purchase-context="$ctrl.purchaseContext" purchase-context-type="$ctrl.purchaseContextType" reservation="r"></reservation-identifier></a></td> + <td data-ng-if="$ctrl.targetContextType === 'subscription'"><a ui-sref="subscriptions.single.view-reservation({ purchaseContextId: $ctrl.purchaseContext.publicIdentifier,reservationId: r.id})"><reservation-identifier purchase-context="$ctrl.purchaseContext" purchase-context-type="$ctrl.purchaseContextType" reservation="r"></reservation-identifier></a></td> + <td ng-bind="$ctrl.formatFullName(r)"></td> + <td ng-bind="r.email"></td> + <td ng-bind="r.paymentMethod"></td> + <td ng-bind="r.paidAmount | money:r.currencyCode"></td> + <td ng-bind="r.transactionTimestamp | formatDate"></td> + <td ng-bind="r.transactionNotes"></td> + <td><button class="btn btn-xs btn-default" ng-click="$ctrl.editPaymentDetails(r.id)"><i class="fa fa-edit" title="Edit"></i></button></td> + </tr> + </tbody> + </table> + <div class="text-center wMarginBottom"> + <uib-pagination ng-change="$ctrl.updateFilteredData()" total-items="$ctrl.foundReservations" ng-model="$ctrl.currentPage" items-per-page="$ctrl.itemsPerPage" max-size="10" force-ellipses="true"></uib-pagination> + </div> + </div> +</div> \ No newline at end of file diff --git a/src/main/webapp/resources/js/admin/feature/payments-list/payments-list.js b/src/main/webapp/resources/js/admin/feature/payments-list/payments-list.js new file mode 100644 index 0000000000..a08cd402dd --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/payments-list/payments-list.js @@ -0,0 +1,73 @@ +(function() { + 'use strict'; + + angular.module('adminApplication').component('paymentsList', { + bindings: { + purchaseContext: '<', + purchaseContextType: '<' + }, + controller: ['PurchaseContextService', '$filter', '$location', '$stateParams', 'AdminReservationService', PaymentsListCtrl], + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/payments-list/payments-list.html' + }); + + + + function PaymentsListCtrl(PurchaseContextService, $filter, $location, $stateParams, AdminReservationService) { + var ctrl = this; + + var currentSearch = $location.search(); + + ctrl.currentPage = currentSearch.page || 1; + ctrl.toSearch = currentSearch.search || $stateParams.search || ''; + + if(!ctrl.targetContextType) { + ctrl.targetContextType = ctrl.purchaseContextType; + } + + ctrl.itemsPerPage = 50; + ctrl.formatFullName = formatFullName; + ctrl.updateFilteredData = loadData; + ctrl.editPaymentDetails = editPaymentDetails; + + this.$onInit = function() { + loadData(); + }; + + function loadData() { + + $location.search({ + page: ctrl.currentPage, + search: ctrl.toSearch + }); + + PurchaseContextService.findAllPayments(ctrl.purchaseContextType, ctrl.purchaseContext.publicIdentifier, ctrl.currentPage - 1, ctrl.toSearch) + .then(function (res) { + ctrl.reservations = res.data.left; + ctrl.foundReservations = res.data.right; + setTimeout(function() { + document.getElementById('filter-reservations').focus(); + }); + }); + + var keys = Object.keys(ctrl.purchaseContext.title); + ctrl.purchaseContextTitle = ctrl.purchaseContext.title[keys[0]]; + } + + function formatFullName(r) { + if(r.firstName && r.lastName) { + return r.firstName + ' ' + r.lastName; + } else { + return r.fullName; + } + } + + function editPaymentDetails(reservationId) { + PurchaseContextService.editPaymentDetails(reservationId, ctrl.purchaseContextType, ctrl.purchaseContext.publicIdentifier) + .then(function(){ + loadData(); + }, function(err) { + console.error('error', err); + }); + } + } +})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/admin/feature/project-banner/project-banner.html b/src/main/webapp/resources/js/admin/feature/project-banner/project-banner.html index 66c6bb3527..8bc3f9bc13 100644 --- a/src/main/webapp/resources/js/admin/feature/project-banner/project-banner.html +++ b/src/main/webapp/resources/js/admin/feature/project-banner/project-banner.html @@ -1,6 +1,6 @@ <div ng-if="!$ctrl.fullBanner"> <h5 class="text-muted">Powered by <a href="https://alf.io" target="_blank">Alf.io</a> v.{{$ctrl.alfioVersion}}</h5> - <small><a href="https://github.com/alfio-event/alf.io/issues" target="_blank" rel="noopener">report an issue</a> or <a href="https://groups.google.com/d/forum/alfio" target="_blank" rel="noopener">ask for help</a></small> + <small><a href="https://github.com/alfio-event/alf.io/issues" target="_blank" rel="noopener">report an issue</a> or <a href="https://github.com/alfio-event/alf.io/discussions" target="_blank" rel="nofollow noopener noreferrer">ask for help</a></small> <div class="wMarginTop10px"> <a class="btn btn-xs btn-success" href="https://alf.io/usage-form/" target="_blank" rel="noopener">Tell us about you!</a> </div> diff --git a/src/main/webapp/resources/js/admin/feature/promo-codes/edit-promo-code-modal.html b/src/main/webapp/resources/js/admin/feature/promo-codes/edit-promo-code-modal.html index b4b68ed525..7652a6bbd8 100644 --- a/src/main/webapp/resources/js/admin/feature/promo-codes/edit-promo-code-modal.html +++ b/src/main/webapp/resources/js/admin/feature/promo-codes/edit-promo-code-modal.html @@ -15,7 +15,7 @@ <h3 data-ng-if="promocode.codeType === 'ACCESS'">Insert new Access Code</h3> <div class="form-group" bs-form-error="promocode.dateString"> <label for="date">Validity range <span ng-if="!forEvent">(Note: these dates are in your current timezone)</span></label> - <div class="input-group"> + <div ng-class="{'input-group': forEvent}"> <input type="text" data-date-range data-start-model="promocode.start" min-date="true" data-end-model="promocode.end" data-ng-model="promocode.dateString" name="date" id="date" class="form-control" required /> <div class="input-group-addon" ng-if="forEvent"> <span>{{event.timeZone}}</span> @@ -29,13 +29,18 @@ <h3 data-ng-if="promocode.codeType === 'ACCESS'">Insert new Access Code</h3> <select class="form-control" id="discountType" ng-options="key as value for (key , value) in discountTypes" ng-model="promocode.discountType"></select> </div> + <div ng-if="promocode.codeType === 'DISCOUNT' && promocode.discountType !== 'PERCENTAGE' && !forEvent && !promocode.currencyCode" class="form-group"> + <label for="promoCode">Currency</label> + <input id="currency" autocomplete="off" type="text" ng-model="promocode.currencyCode" class="form-control" uib-typeahead="currency.code as currency.name for currency in currencies | filter:$viewValue | limitTo:8" required pattern="[A-Z]{3}"> + </div> + <div class="form-group" bs-form-error="promocode.discountAmount" ng-if="promocode.codeType == 'DISCOUNT'"> <label for="promoCode">Discount amount</label> <div class="input-group"> <input type="number" id="discountAmount" data-ng-model="promocode.discountAmount" class="form-control" min="0" required/> <div class="input-group-addon"> <span data-ng-if="promocode.discountType === 'PERCENTAGE'">%</span> - <span data-ng-if="promocode.discountType !== 'PERCENTAGE'">{{event.currency}}<span data-ng-if="promocode.discountType === 'FIXED_AMOUNT'"> (per ticket)</span></span> + <span data-ng-if="promocode.discountType !== 'PERCENTAGE' && (forEvent || promocode.currencyCode)">{{ promocode.currencyCode || event.currency }}<span data-ng-if="promocode.discountType === 'FIXED_AMOUNT'"> (per ticket)</span></span> </div> </div> <field-error data-form-obj="discountAmount" data-field-obj="promocode.discountAmount" data-show-existing-errors="showExistingErrors"></field-error> diff --git a/src/main/webapp/resources/js/admin/feature/promo-codes/list.html b/src/main/webapp/resources/js/admin/feature/promo-codes/list.html index 72b4225e30..803c1ba908 100644 --- a/src/main/webapp/resources/js/admin/feature/promo-codes/list.html +++ b/src/main/webapp/resources/js/admin/feature/promo-codes/list.html @@ -16,7 +16,7 @@ </thead> <tbody> <tr data-ng-repeat="promocode in $ctrl.promocodes" data-ng-class="{'text-muted': promocode.expired}"> - <td ng-if="promocode.useCount > 0"><a data-ui-sref="events.single.reservationsList({eventName: $ctrl.event.shortName, search: promocode.promoCode})" title="Find all reservations">{{::promocode.promoCode}}</a> <span data-ng-if="promocode.expired">(Expired)</span></td> + <td ng-if="promocode.useCount > 0"><a ng-click="$ctrl.openPromoCodeDetails(promocode)" title="Open usage details">{{::promocode.promoCode}}</a> <span data-ng-if="promocode.expired">(Expired)</span></td> <td ng-if="!promocode.useCount || promocode.useCount === 0">{{::promocode.promoCode}} <span data-ng-if="promocode.expired">(Expired)</span><span data-ng-if="!promocode.expired && promocode.codeType === 'DYNAMIC'">(auto)</span></td> <td class="text-right">{{promocode.useCount}}</td> <td class="text-right">{{promocode.maxUsage}}</td> @@ -24,7 +24,7 @@ <td>{{::promocode.formattedEnd | formatDate}}</td> <td ng-if="!$ctrl.isAccess"> <span data-ng-if="!$ctrl.isAccess && promocode.discountType === 'PERCENTAGE'">-{{::promocode.discountAmount}}%</span> - <span data-ng-if="!$ctrl.isAccess && promocode.discountType !== 'PERCENTAGE'">-{{::promocode.formattedDiscountAmount | money : ($ctrl.event.currency || "")}} <div data-ng-if="promocode.discountType === 'FIXED_AMOUNT'"> per ticket</div></span> + <span data-ng-if="!$ctrl.isAccess && promocode.discountType !== 'PERCENTAGE'">-<span ng-if="::promocode.hasCurrency">{{::promocode.formattedDiscountAmount | money : (promocode.currencyToDisplay || "")}}</span> <div data-ng-if="promocode.discountType === 'FIXED_AMOUNT'"> per ticket</div></span> </td> <td ng-if="$ctrl.forEvent"> <div ng-if="promocode.categories.length > 0"><p ng-repeat="categoryId in promocode.categories">{{::$ctrl.ticketCategoriesById[categoryId].name}}</p></div> diff --git a/src/main/webapp/resources/js/admin/feature/promo-codes/promo-codes.js b/src/main/webapp/resources/js/admin/feature/promo-codes/promo-codes.js index 58c61c174f..42a23d607b 100644 --- a/src/main/webapp/resources/js/admin/feature/promo-codes/promo-codes.js +++ b/src/main/webapp/resources/js/admin/feature/promo-codes/promo-codes.js @@ -3,7 +3,7 @@ angular.module('adminApplication') .component('promoCodes', { - controller: ['$window', '$uibModal', '$q', 'PromoCodeService', 'ConfigurationService', PromoCodeCtrl], + controller: ['$window', '$uibModal', '$q', 'PromoCodeService', 'ConfigurationService', 'UtilsService', PromoCodeCtrl], templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/promo-codes/promo-codes.html', bindings: { forEvent: '<', @@ -13,7 +13,7 @@ } }) .component('promoCodeList', { - controller: [PromoCodeListCtrl], + controller: ['$uibModal', 'PromoCodeService', 'NotificationHandler', PromoCodeListCtrl], templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/promo-codes/list.html', bindings: { forEvent: '<', @@ -28,9 +28,36 @@ } }); - function PromoCodeListCtrl() {} + function PromoCodeListCtrl($uibModal, PromoCodeService, NotificationHandler) { + var ctrl = this; + ctrl.openPromoCodeDetails = openPromoCodeDetails; - function PromoCodeCtrl($window, $uibModal, $q, PromoCodeService, ConfigurationService) { + function openPromoCodeDetails(promoCode) { + PromoCodeService.getUsageDetails(promoCode.id, ctrl.event ? ctrl.event.shortName : '') + .then(function(result) { + var data = result.data; + if (data.length === 0) { + NotificationHandler.showError('No reservations found.'); + } else { + $uibModal.open({ + size: 'lg', + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/promo-codes/usage-details.html', + backdrop: 'static', + controllerAs: '$ctrl', + bindToController: true, + controller: function($scope) { + this.data = data; + this.close = function() { + $scope.$dismiss(); + } + } + }); + } + }); + } + } + + function PromoCodeCtrl($window, $uibModal, $q, PromoCodeService, ConfigurationService, UtilsService) { var ctrl = this; ctrl.isInternal = isInternal; @@ -55,15 +82,22 @@ ctrl.promoCodeDescription = 'Promo'; } - loader().then(function(res) { - ctrl.promocodes = res.data.filter(function(pc) { + $q.all([loader(), UtilsService.getAvailableCurrencies()]).then(function(results) { + var pcRes = results[0]; + ctrl.currencies = results[1].data; + ctrl.promocodes = pcRes.data.filter(function(pc) { return pc.codeType === 'DISCOUNT' || pc.codeType === 'DYNAMIC'; }); - ctrl.accesscodes = res.data.filter(function(pc) { + ctrl.accesscodes = pcRes.data.filter(function(pc) { return pc.codeType === 'ACCESS'; }); - angular.forEach(ctrl.promocodes, function(v) { + var discountOrAccess = pcRes.data.filter(function(pc) { + return pc.codeType === 'DISCOUNT' || pc.codeType === 'ACCESS'; + }); + angular.forEach(discountOrAccess, function(v) { (function(v) { + v.hasCurrency = ctrl.forEvent || v.currencyCode; + v.currencyToDisplay = ctrl.forEvent ? ctrl.event.currency : v.currencyCode; PromoCodeService.countUse(v.id).then(function(val) { v.useCount = parseInt(val.data, 10); }); @@ -105,8 +139,6 @@ } function changeDate(promocode) { - - //TODO: transform component style $uibModal.open({ size: 'lg', templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/promo-codes/edit-date-promo-code-modal.html', @@ -188,6 +220,7 @@ $scope.event = event; $scope.forEvent = forEvent; $scope.promoCodeDescription = ctrl.promoCodeDescription; + $scope.currencies = ctrl.currencies; var now = moment(); var eventBegin = forEvent ? moment(event.formattedBegin) : moment().add(1,'d').endOf('d'); @@ -217,6 +250,10 @@ hiddenCategoryId: null }; + if ($scope.forEvent) { + $scope.promocode.currencyCode = $scope.event.currency; + } + $scope.addCategory = function addCategory(index, value) { $scope.promocode.categories[index] = value; }; diff --git a/src/main/webapp/resources/js/admin/feature/promo-codes/usage-details.html b/src/main/webapp/resources/js/admin/feature/promo-codes/usage-details.html new file mode 100644 index 0000000000..5738938d16 --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/promo-codes/usage-details.html @@ -0,0 +1,61 @@ +<div class="modal-content"> + + <div class="modal-body"> + <div ng-repeat="detail in $ctrl.data"> + <div class="page-header"> + <h3>Event: <em>{{detail.event.displayName}}</em></h3> + </div> + + <table class="table usage-details"> + <caption class="sr-only">Reservations for event detail</caption> + <thead> + <tr> + <th>ID</th> + <th>Customer</th> + <th>Payment</th> + <th>Amount</th> + <th>Confirmation</th> + </tr> + </thead> + <tbody ng-repeat="reservation in detail.reservations" ng-class-odd="'bg-gray'"> + <tr> + <td><a ui-sref="events.single.view-reservation({ eventName : detail.event.shortName, reservationId: reservation.id})" target="_blank"><reservation-identifier purchase-context="detail.event" purchase-context-type="'event'" reservation="reservation"></reservation-identifier></a></td> + <td>{{reservation.firstName}} {{reservation.lastName}} <{{reservation.email}}></td> + <td>{{reservation.paymentType}}</td> + <td><span ng-if="reservation.paymentType !== 'NONE'">{{reservation.currency}} {{reservation.formattedAmount}}</span></td> + <td>{{reservation.confirmationTimestamp | formatDate}}</td> + </tr> + <tr> + <td> </td> + <td colspan="4"> + <table class="table transparent"> + <caption class="sr-only">Tickets</caption> + <thead> + <tr> + <th style="font-weight: normal">Ticket ID</th> + <th style="font-weight: normal">Attendee</th> + <th style="font-weight: normal">Type</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="ticket in reservation.tickets"> + <td>{{ticket.id | limitTo:8 | uppercase}}</td> + <td>{{ticket.firstName}} {{ticket.lastName}}</td> + <td>{{ticket.type}}</td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="modal-footer"> + <div class="row"> + <div class="col-md-4 col-md-push-8 col-xs-12"> + <button class="btn btn-lg btn-warning btn-block" style="margin-bottom: 10px" ng-click="$ctrl.close()">Close</button> + </div> + </div> + </div> +</div> diff --git a/src/main/webapp/resources/js/admin/feature/reservation-cancel/reservation-cancel.js b/src/main/webapp/resources/js/admin/feature/reservation-cancel/reservation-cancel.js index 01d1536279..6f0e9028e4 100644 --- a/src/main/webapp/resources/js/admin/feature/reservation-cancel/reservation-cancel.js +++ b/src/main/webapp/resources/js/admin/feature/reservation-cancel/reservation-cancel.js @@ -36,7 +36,7 @@ $scope.purchaseContext = purchaseContext; $scope.purchaseContextType = purchaseContextType; $scope.reservationId = reservation.id; - $scope.invoiceRequested = reservation.customerData.invoiceRequested; + $scope.invoiceRequested = reservation.customerData.invoiceRequested && reservation.status !== 'OFFLINE_PAYMENT'; $scope.credit = credit; $scope.close = function() { $scope.$dismiss(false); diff --git a/src/main/webapp/resources/js/admin/feature/reservation/reservation.service.js b/src/main/webapp/resources/js/admin/feature/reservation/reservation.service.js index e6ab72108a..ca60c9589f 100644 --- a/src/main/webapp/resources/js/admin/feature/reservation/reservation.service.js +++ b/src/main/webapp/resources/js/admin/feature/reservation/reservation.service.js @@ -2,7 +2,7 @@ 'use strict'; angular.module('adminApplication') - .service('AdminReservationService', ['$http', 'HttpErrorHandler', function($http, HttpErrorHandler) { + .service('AdminReservationService', ['$http', 'HttpErrorHandler', '$uibModal', function($http, HttpErrorHandler, $uibModal) { return { createReservation: function(eventName, reservation) { return $http.post('/admin/api/reservation/event/'+eventName+'/new', reservation).error(HttpErrorHandler.handle); @@ -48,6 +48,32 @@ }, getTicket: function(publicIdentifier, reservationId, ticketId) { return $http.get('/admin/api/reservation/event/'+publicIdentifier+'/'+reservationId+'/ticket/'+ticketId).error(HttpErrorHandler.handle); + }, + getTicketIdsWithAdditionalData: function(publicIdentifier, reservationId) { + return $http.get('/admin/api/reservation/event/'+publicIdentifier+'/'+reservationId+'/tickets-with-additional-data').error(HttpErrorHandler.handle); + }, + openFullDataView: function(purchaseContext, reservationId, ticketUUID) { + return $uibModal.open({ + size:'md', + template:'<ticket-full-data event="event" reservation-id="reservationId" ticket-id="ticketUUID" on-success="success(result)" on-cancel="close()"></ticket-full-data>', + backdrop: 'static', + controller: function($scope) { + $scope.event = purchaseContext; + $scope.ticketUUID = ticketUUID; + $scope.reservationId = reservationId; + $scope.close = function() { + $scope.$close(false); + }; + + $scope.success = function () { + $scope.$close(false); + } + } + }).result; + }, + getFullTicketData: function(purchaseContext, reservationId, ticketUUID) { + return $http.get('/admin/api/reservation/event/' + purchaseContext.publicIdentifier + '/' + reservationId + '/ticket/'+ticketUUID+'/full-data') + .error(HttpErrorHandler.handle); } } }]).service('AdminImportService', ['$http', 'HttpErrorHandler', function($http, HttpErrorHandler) { diff --git a/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.html b/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.html index 56952f0567..10a21c8d29 100644 --- a/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.html +++ b/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.html @@ -458,7 +458,8 @@ <h3>Order summary</h3> </button> </td> <td> - <button type="button" class="btn btn-sm btn-danger" ng-click="$ctrl.removeTicket(attendee)"><i class="fa fa-trash"></i> <span class="hidden-xs hidden-sm">Remove</span></button> + <button type="button" class="btn btn-sm btn-default" data-ng-if="attendee.hasAdditionalData" ng-click="$ctrl.openFullData(attendee)" ><i class="fa fa-list"></i> <span class="hidden-xs hidden-sm">Full data</span></button> + <button type="button" class="btn btn-sm btn-danger" data-ng-if="attendee.status !== 'CHECKED_IN'" ng-click="$ctrl.removeTicket(attendee)"><i class="fa fa-trash"></i> <span class="hidden-xs hidden-sm">Remove</span></button> </td> </tr> </tbody> diff --git a/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.js b/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.js index 134879a39e..edb8977a58 100644 --- a/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.js +++ b/src/main/webapp/resources/js/admin/feature/reservation/view/reservation-view.js @@ -85,10 +85,15 @@ } else if(['INCLUDED_EXEMPT', 'NOT_INCLUDED_EXEMPT'].indexOf(src.vatStatus) > -1) { vatApplied = 'N'; } + var containsCheckedInTickets = ctrl.reservationDescriptor.ticketsByCategory.some(function(category) { + return category.value.some(function(t) { + return t.status === 'CHECKED_IN'; + }) + }); ctrl.reservation = { id: src.id, status: src.status, - showCreditCancel: src.status !== 'CANCELLED' && src.status !== 'CREDIT_NOTE_ISSUED', + showCreditCancel: src.status !== 'CANCELLED' && src.status !== 'CREDIT_NOTE_ISSUED' && !containsCheckedInTickets, expirationStr: moment(src.validity).format('YYYY-MM-DD HH:mm'), expiration: { date: moment(src.validity).format('YYYY-MM-DD'), @@ -131,6 +136,20 @@ } }); + if(ctrl.purchaseContextType === 'event') { + // retrieve tickets with additional data + AdminReservationService.getTicketIdsWithAdditionalData(ctrl.purchaseContext.publicIdentifier, ctrl.reservation.id) + .then(function(result) { + if(result.data && result.data.length > 0) { + ctrl.reservation.ticketsInfo.forEach(function(ticketInfo) { + ticketInfo.attendees.forEach(function (attendee) { + attendee.hasAdditionalData = result.data.indexOf(attendee.ticketId) > -1; + }); + }); + } + }); + } + ctrl.displayConfirmButton = ['PENDING', 'OFFLINE_PAYMENT', 'STUCK'].indexOf(ctrl.reservation.status) > -1; CountriesService.getCountries().then(function(countries) { @@ -398,14 +417,25 @@ }); }; + ctrl.openFullData = function(ticket) { + AdminReservationService.openFullDataView(ctrl.purchaseContext, ctrl.reservation.id, ticket.uuid) + .then(function() { + console.log('modal closed.'); + }) + }; + ctrl.removeTicket = function(ticket) { - EventService.removeTicketModal(ctrl.purchaseContext, ctrl.reservation.id, ticket.ticketId, ctrl.reservation.customerData.invoiceRequested).then(function(billingDocumentRequested) { - var message = 'Ticket has been cancelled.'; - if(billingDocumentRequested) { - message += ' A credit note has been generated. Please check the Billing Documents tab.'; - } - reloadReservation(message); - }); + if (ticket.status !== 'CHECKED_IN') { + EventService.removeTicketModal(ctrl.purchaseContext, ctrl.reservation.id, ticket.ticketId, ctrl.reservation.customerData.invoiceRequested).then(function(billingDocumentRequested) { + var message = 'Ticket has been cancelled.'; + if(billingDocumentRequested) { + message += ' A credit note has been generated. Please check the Billing Documents tab.'; + } + reloadReservation(message); + }); + } else { + NotificationHandler.showError('Cannot remove a checked-in ticket'); + } }; ctrl.confirmRefund = function() { diff --git a/src/main/webapp/resources/js/admin/feature/subscriptions/edit.html b/src/main/webapp/resources/js/admin/feature/subscriptions/edit.html index 6bfd13f51c..cc422a6606 100644 --- a/src/main/webapp/resources/js/admin/feature/subscriptions/edit.html +++ b/src/main/webapp/resources/js/admin/feature/subscriptions/edit.html @@ -37,8 +37,8 @@ <h3>Description</h3> <div class="col-xs-12 col-sm-6" data-ng-repeat="lang in $ctrl.selectedLanguages"> <div class="form-group" bs-form-error="editSubscriptionForm['description-'+lang.locale]"> <label for="description-{{lang.locale}}">{{lang.displayLanguage}}</label><display-commonmark-preview class="pull-right" style="margin-right: 20px" text="$ctrl.subscription.description[lang.locale]"></display-commonmark-preview> - <textarea ng-model="$ctrl.subscription.description[lang.locale]" name="description-{{lang.locale}}" id="description-{{lang.locale}}" required class="form-control" ng-minlength="10" data-ng-maxlength="255"></textarea> - <field-error data-form-obj="editSubscriptionForm" data-field-obj="editSubscriptionForm['description-'+lang.locale]" data-min-char="10" data-max-char="255" data-show-existing-errors="true"></field-error> + <textarea ng-model="$ctrl.subscription.description[lang.locale]" name="description-{{lang.locale}}" id="description-{{lang.locale}}" required class="form-control" ng-minlength="10" data-ng-maxlength="{{$ctrl.descriptionMaxLength}}"></textarea> + <field-error data-form-obj="editSubscriptionForm" data-field-obj="editSubscriptionForm['description-'+lang.locale]" data-min-char="10" data-max-char="{{$ctrl.descriptionMaxLength}}" data-show-existing-errors="true"></field-error> </div> </div> </div> @@ -82,7 +82,7 @@ <h3>Image</h3> data-accept="image/*" data-ngf-pattern="'image/*'" data-ng-model="$ctrl.droppedFile" data-ngf-drop data-ngf-select data-ngf-multiple="false" data-ngf-allow-dir="false" data-ngf-drag-over-class="'drop-file-zone-hover'"> - Drop image here or click to upload (Maximum size : 200KB) + Drop image here or click to upload (Maximum size : 1MB) </div> </div> </div> diff --git a/src/main/webapp/resources/js/admin/feature/subscriptions/subscriptions.js b/src/main/webapp/resources/js/admin/feature/subscriptions/subscriptions.js index 1a44940195..0faabf41a3 100644 --- a/src/main/webapp/resources/js/admin/feature/subscriptions/subscriptions.js +++ b/src/main/webapp/resources/js/admin/feature/subscriptions/subscriptions.js @@ -266,7 +266,8 @@ UtilsService.getAvailableCurrencies(), PaymentProxyService.getAllProxies(ctrl.organizationId), LocationService.getTimezones(), - ConfigurationService.loadSingleConfigForOrganization(ctrl.organizationId, 'GENERATE_TICKETS_FOR_SUBSCRIPTIONS') + ConfigurationService.loadSingleConfigForOrganization(ctrl.organizationId, 'GENERATE_TICKETS_FOR_SUBSCRIPTIONS'), + ConfigurationService.loadInstanceSettings() ]; if(ctrl.subscriptionId) { ctrl.existing = true; @@ -292,6 +293,7 @@ ctrl.paymentMethods = getPaymentMethods(res[3].data); ctrl.timeZones = res[4].data; ctrl.ticketsGenerationJobActive = res[5].data === 'true'; + ctrl.descriptionMaxLength = res[6].data.descriptionMaxLength; if(ctrl.existing) { initExistingSubscription(); @@ -451,6 +453,9 @@ return $http.get('/admin/api/reservation/subscription/'+name+'/reservations/list', {params: {page: page, search: search, status: status}}); }, loadSubscriptionsDescriptors: function(organizationId) { + if (!window.USER_IS_OWNER) { + return $q.reject('not authorized'); + } return $http.get('/admin/api/organization/'+organizationId+'/subscription/list') .error(HttpErrorHandler.handle); }, diff --git a/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category-detail.html b/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category-detail.html index d0f157a943..0b95e3d29b 100644 --- a/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category-detail.html +++ b/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category-detail.html @@ -7,7 +7,7 @@ <h3>{{$ctrl.ticketCategory.name}}</h3> <div class="col-xs-4"> <div class="pull-right"> <a class="btn btn-sm btn-default" ng-click="$ctrl.ticketCategory.error = null; $ctrl.editHandler($ctrl.ticketCategory)"><i class="fa fa-edit"></i> edit</a> - <a class="btn btn-sm btn-danger" ng-click="$ctrl.removeHandler($ctrl.ticketCategory)"><i class="fa fa-trash"></i> delete</a> + <a class="btn btn-sm btn-danger" ng-click="$ctrl.removeHandler($ctrl.ticketCategory)" data-ng-if="$ctrl.deleteEnabled"><i class="fa fa-trash"></i> delete</a> <a class="btn btn-default btn-sm" ng-if="$ctrl.swapEnabled && !$ctrl.isFirst" data-ng-click="$ctrl.swapHandler($ctrl.ticketCategory, true)"><i class="fa fa-arrow-up"></i> move up</a> <a class="btn btn-default btn-sm" ng-if="$ctrl.swapEnabled && !$ctrl.isLast" data-ng-click="$ctrl.swapHandler($ctrl.ticketCategory, false)"><i class="fa fa-arrow-down"></i> move down</a> </div> @@ -37,7 +37,7 @@ <h5> </span> <i class="ml-1 fa fa fa-money fa-1_5x" ng-if="!$ctrl.event.freeOfCharge"></i><span class="sr-only">Ticket Price</span> <span class="text-muted" ng-if="!$ctrl.event.freeOfCharge"> - {{$ctrl.ticketCategory.price | money : ($ctrl.event.currency || "")}} <span ng-if="!$ctrl.event.vatIncluded"><i class="text-muted">+ VAT</i></span> + {{$ctrl.ticketCategory.price | money : ($ctrl.event.currency || "")}} <span ng-if="$ctrl.plusVat"><i class="text-muted">+ VAT</i></span> </span> </h5> <h5 ng-if="$ctrl.hasCustomCheckIn()"> diff --git a/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category.js b/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category.js index eefba6f8f7..1ba8f0d8db 100644 --- a/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category.js +++ b/src/main/webapp/resources/js/admin/feature/ticket-category/ticket-category.js @@ -21,8 +21,16 @@ function TicketCategoryDetailCtrl() { var ctrl = this; + ctrl.deleteEnabled = !angular.isDefined(ctrl.event.id) || ctrl.event.ticketCategories.length > 1; + ctrl.baseUrl = window.location.origin; + var applyTaxes = ctrl.ticketCategory.price > 0 && (ctrl.ticketCategory.configuration || []).findIndex(function(c) { + return c.key === 'APPLY_TAX_TO_CATEGORY' && c.value === 'false' + }) === -1; + + ctrl.plusVat = applyTaxes && !ctrl.event.vatIncluded; + ctrl.categoryHasDescriptions = function(category) { return category && category.description ? Object.keys(category.description).length > 0 : false; }; diff --git a/src/main/webapp/resources/js/admin/feature/ticket-full-data/ticket-full-data.html b/src/main/webapp/resources/js/admin/feature/ticket-full-data/ticket-full-data.html new file mode 100644 index 0000000000..c8e986de1e --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/ticket-full-data/ticket-full-data.html @@ -0,0 +1,52 @@ +<div class="modal-header"> + <h3>Ticket full data</h3> +</div> +<div class="modal-body"> + + <div class="row" data-ng-repeat="item in $ctrl.fullData.ticketFieldConfigurationBeforeStandard"> + <div class="col-xs-12"> + <div class="form-group"> + <label>{{$ctrl.getFieldLabel(item)}}</label> + <div class="form-control-static">{{$ctrl.getFieldValue(item)}}</div> + </div> + </div> + </div> + + <div class="row"> + <div class="col-xs-6"> + <div class="form-group"> + <label>First name</label> + <div class="form-control-static">{{$ctrl.fullData.firstName}}</div> + </div> + </div> + <div class="col-xs-6"> + <div class="form-group"> + <label>Last name</label> + <div class="form-control-static">{{$ctrl.fullData.lastName}}</div> + </div> + </div> + <div class="col-xs-12"> + <div class="form-group"> + <label>Email</label> + <div class="form-control-static">{{$ctrl.fullData.email}}</div> + </div> + </div> + </div> + + <div class="row" data-ng-repeat="item in $ctrl.fullData.ticketFieldConfigurationAfterStandard"> + <div class="col-xs-12"> + <div class="form-group"> + <label>{{$ctrl.getFieldLabel(item)}}</label> + <div class="form-control-static">{{$ctrl.getFieldValue(item)}}</div> + </div> + </div> + </div> + +</div> +<div class="modal-footer"> + <div class="row"> + <div class="col-md-4 col-md-push-8 col-xs-12"> + <button class="btn btn-lg btn-default btn-block btn-block-xs" type="button" data-ng-click="$ctrl.onCancel()">Close</button> + </div> + </div> +</div> diff --git a/src/main/webapp/resources/js/admin/feature/ticket-full-data/ticket-full-data.js b/src/main/webapp/resources/js/admin/feature/ticket-full-data/ticket-full-data.js new file mode 100644 index 0000000000..38178a41d5 --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/ticket-full-data/ticket-full-data.js @@ -0,0 +1,43 @@ +(function() { + 'use strict'; + + angular.module('adminApplication').component('ticketFullData', { + controller: ['AdminReservationService', 'EventService', TicketsFullDataCtrl], + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/ticket-full-data/ticket-full-data.html', + bindings: { + event: '<', + reservationId: '<', + ticketId:'<', + canGenerateCreditNote: '<', + onSuccess: '&', + onCancel:'&' + } + }); + + + function TicketsFullDataCtrl(AdminReservationService) { + var ctrl = this; + var defaultLang = ctrl.event.contentLanguages[0].locale; + var textFields = ['input:text', 'input:tel', 'textarea', 'vat:eu']; + ctrl.$onInit = function() { + AdminReservationService.getFullTicketData(ctrl.event, ctrl.reservationId, ctrl.ticketId).then(function(res) { + ctrl.fullData = res.data; + console.log(res.data); + }); + }; + ctrl.getFieldValue = function(field) { + if(textFields.indexOf(field.type) > -1) { + return field.value; + } else if (field.description[defaultLang]) { + return field.description[defaultLang].restrictedValuesDescription[field.value] || field.value; + } + return field.value; + }; + ctrl.getFieldLabel = function(field) { + if (field.description[defaultLang]) { + return field.description[defaultLang].label; + } + return field.name; + }; + } +})(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.html b/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.html index e9ef6ab605..a2d0bf8548 100644 --- a/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.html +++ b/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.html @@ -41,7 +41,7 @@ <h2>Tickets for {{$ctrl.ticketCategory.name}}</h2> <div class="list-group-item" data-ng-repeat="ticket in $ctrl.tickets | orderBy: ['timestamp', 'transaction.id' , 'uuid']"> <div class="list-group-item-heading"> <button type="button" class="btn btn-sm btn-default pull-right" data-ng-click="$ctrl.toggleLocking($ctrl.event, ticket, $ctrl.ticketCategory)"><span data-ng-if="ticket.lockedAssignment"><i class="fa fa-unlock"></i> unlock</span><span data-uib-tooltip="forbid ticket reassignment" data-tooltip-placement="left" data-ng-if="!ticket.lockedAssignment"><i class="fa fa-lock"></i> lock</span></button> - <button type="button" class="btn btn-sm btn-default pull-right" ng-click="$ctrl.removeTicket($ctrl.event, ticket)"><i class="fa fa-trash"></i> remove</button> + <button type="button" class="btn btn-sm btn-default pull-right" ng-if="ticket.status !== 'CHECKED_IN'" ng-click="$ctrl.removeTicket($ctrl.event, ticket)"><i class="fa fa-trash"></i> remove</button> <h4><i data-ng-class="$ctrl.evaluateTicketStatus(ticket.status)" data-uib-tooltip="{{ticket.status | statusText}}"></i> {{::ticket.uuid | limitTo:8}} <span data-ng-if="ticket.paid">Confirmed on: {{::ticket.transactionTimestamp | formatDate:'DD.MM.YYYY HH:mm:ss'}}</span> by {{::ticket.ticketReservation.fullName}}</h4> </div> <div class="list-group-item-text"> diff --git a/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.js b/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.js index 2bc498b58d..55cd922176 100644 --- a/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.js +++ b/src/main/webapp/resources/js/admin/feature/tickets-list/tickets-list.js @@ -6,13 +6,13 @@ event: '<', categoryId: '<' }, - controller: ['EventService', '$location', TicketsListCtrl], + controller: ['EventService', '$location', 'NotificationHandler', TicketsListCtrl], templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/tickets-list/tickets-list.html' }); - function TicketsListCtrl(EventService, $location) { + function TicketsListCtrl(EventService, $location, NotificationHandler) { var ctrl = this; var currentSearch = $location.search(); @@ -72,9 +72,14 @@ } function removeTicket(event, ticket) { - EventService.removeTicketModal(event, ticket.ticketReservation.id, ticket.id, ticket.ticketReservation.invoiceRequested).then(function() { - loadData(); - }); + if (ticket.status !== 'CHECKED_IN') { + EventService.removeTicketModal(event, ticket.ticketReservation.id, ticket.id, ticket.ticketReservation.invoiceRequested).then(function() { + NotificationHandler.showSuccess('Ticket removed'); + loadData(); + }); + } else { + NotificationHandler.showError('Cannot remove a checked-in ticket'); + } } function loadData() { diff --git a/src/main/webapp/resources/js/admin/feature/tickets-remove/tickets-remove.js b/src/main/webapp/resources/js/admin/feature/tickets-remove/tickets-remove.js index 4b581e15fa..84b1739016 100644 --- a/src/main/webapp/resources/js/admin/feature/tickets-remove/tickets-remove.js +++ b/src/main/webapp/resources/js/admin/feature/tickets-remove/tickets-remove.js @@ -35,8 +35,13 @@ function confirmRemove() { ctrl.submitted = true; - return EventService.removeTickets(ctrl.event.shortName, ctrl.reservationId, [ctrl.ticketId], ctrl.toRefund, ctrl.notify, ctrl.issueCreditNote).then(function() { - ctrl.onSuccess({ result: ctrl.issueCreditNote }); + if (!ctrl.paymentInfo.supportRefund && ctrl.canGenerateCreditNote && ctrl.issueCreditNote) { + // if payment provider does not support refund, but credit note has been requested, + // we simulate a refund anyway, otherwise credit note does + ctrl.toRefund = [ctrl.ticketId]; + } + return EventService.removeTickets(ctrl.event.shortName, ctrl.reservationId, [ctrl.ticketId], ctrl.toRefund, ctrl.notify, ctrl.issueCreditNote).then(function(result) { + ctrl.onSuccess({ result: result.data.data.creditNoteGenerated }); }).finally(function() { ctrl.submitted = false; }); diff --git a/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.html b/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.html index 2ce9c34563..11940b2e94 100644 --- a/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.html +++ b/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.html @@ -17,10 +17,11 @@ <h4><span ng-if="$ctrl.user.target == 'USER'">User</span> Role:</h4> Can view and update all the events. Can also create other users and access the configuration at organization and event level. </div> </li> - <li class="list-group-item" ng-if="$ctrl.user.target == 'USER'"> + <li class="list-group-item"> <div class="list-group-item-heading"><strong>Check-in supervisor</strong></div> <div class="list-group-item-text"> - Has a limited access to the events (e.g. can't see the statistics). Can also perform the check-in using the web interface. + <span ng-if="$ctrl.user.target == 'USER'">Has limited access to the events (e.g. can't see the statistics). Can also perform the check-in using the web interface.</span> + <span ng-if="$ctrl.user.target == 'API_KEY'">Can perform check-in and additionally search users from the mobile app. Access on the web admin is denied</span> </div> </li> <li class="list-group-item" ng-if="$ctrl.user.target == 'API_KEY'"> diff --git a/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.js b/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.js index c983a2033b..09aff30f4d 100644 --- a/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.js +++ b/src/main/webapp/resources/js/admin/feature/user-edit/user-edit.js @@ -36,7 +36,7 @@ $q.all([OrganizationService.getAllOrganizations(), UserService.getAllRoles()]).then(function(results) { ctrl.organizations = results[0].data; - ctrl.roles = _.filter(results[1].data, function(r) { return r.target === ctrl.user.target; }); + ctrl.roles = _.filter(results[1].data, function(r) { return (r.target || []).indexOf(ctrl.user.target) > -1; }); }); if(ctrl.type === 'edit') { diff --git a/src/main/webapp/resources/js/admin/feature/users/confirm-api-key-rotation.html b/src/main/webapp/resources/js/admin/feature/users/confirm-api-key-rotation.html new file mode 100644 index 0000000000..1abd120927 --- /dev/null +++ b/src/main/webapp/resources/js/admin/feature/users/confirm-api-key-rotation.html @@ -0,0 +1,20 @@ +<div class="modal-content"> + <div class="modal-header"> + <h1>Confirm API Key rotation</h1> + </div> + <div class="modal-body"> + <p>If you confirm this operation, the System API Key will be regenerated.</p> + <p>Effective immediately, all external applications are required to use the new API Key</p> + <p>This operation <strong>cannot be undone</strong>.</p> + </div> + <div class="modal-footer"> + <div class="row"> + <div class="col-md-4 col-md-push-8 col-xs-12"> + <button class="btn btn-lg btn-warning btn-block" data-ng-click="confirm()" style="margin-bottom: 10px">Confirm</button> + </div> + <div class="col-md-4 col-md-pull-4 col-xs-12"> + <button type="button" class="btn btn-lg btn-default btn-block" data-ng-click="cancel()" style="margin-bottom: 10px">Cancel</button> + </div> + </div> + </div> +</div> diff --git a/src/main/webapp/resources/js/admin/feature/users/users.html b/src/main/webapp/resources/js/admin/feature/users/users.html index 1ae0d1de1d..57f2588879 100644 --- a/src/main/webapp/resources/js/admin/feature/users/users.html +++ b/src/main/webapp/resources/js/admin/feature/users/users.html @@ -1,4 +1,25 @@ <div class="container"> + <div class="wMarginBottom30px" data-ng-if="$ctrl.type === 'apikey' && $ctrl.isAdmin"> + <div class="page-header"> + <h1>System API Key</h1> + <small>System API Key can be used from external application to create/modify/delete organizations and to create API Keys</small> + </div> + <div class="row"> + <div class="col-xs-12"> + <div class="row"> + <div class="col-xs-9" data-ng-if="$ctrl.systemApiKey"><pre>{{$ctrl.systemApiKey}}</pre></div> + <div class="col-xs-1" data-ng-if="$ctrl.systemApiKey"><button class="btn btn-default btn-block" ng-click="$ctrl.copySystemApiKey()">Copy</button></div> + <div class="col-xs-1"> + <button class="btn btn-success btn-block" data-ng-class="{'btn-success': !$ctrl.systemApiKey, 'btn-warning': $ctrl.systemApiKey}"> + <span ng-if="!$ctrl.systemApiKey" ng-click="$ctrl.revealSystemApiKey()">Reveal</span> + <span ng-if="$ctrl.systemApiKey" ng-click="$ctrl.rotateSystemApiKey()">Rotate</span> + </button> + </div> + </div> + </div> + </div> + </div> + <div class="wMarginBottom30px" data-ng-if="$ctrl.type === 'apikey' && $ctrl.isAdmin"> </div> <h1>{{$ctrl.title}}</h1> <hr> <div class="row"> diff --git a/src/main/webapp/resources/js/admin/feature/users/users.js b/src/main/webapp/resources/js/admin/feature/users/users.js index cd36f41c4f..ab8ed081ff 100644 --- a/src/main/webapp/resources/js/admin/feature/users/users.js +++ b/src/main/webapp/resources/js/admin/feature/users/users.js @@ -2,7 +2,7 @@ 'use strict'; angular.module('adminApplication').component('users', { - controller: ['$window', 'UserService', '$uibModal', '$q', UsersCtrl], + controller: ['$window', 'UserService', '$uibModal', '$q', 'NotificationHandler', UsersCtrl], templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/users/users.html', bindings: { title: '@', @@ -30,7 +30,7 @@ - function UsersCtrl($window, UserService, $uibModal, $q) { + function UsersCtrl($window, UserService, $uibModal, $q, NotificationHandler) { var ctrl = this; ctrl.loadUsers = loadUsers; @@ -39,13 +39,17 @@ ctrl.enable = enable; ctrl.downloadApiKeys = downloadAllApiKeys; ctrl.viewApiKey = viewApiKeyQR; + ctrl.rotateSystemApiKey = rotateSystemApiKey; + ctrl.revealSystemApiKey = revealSystemApiKey; + ctrl.copySystemApiKey = copySystemApiKey; ctrl.selectedOrganization = null; ctrl.$onInit = function() { ctrl.users = []; ctrl.organizations = []; - UserService.getAllRoles().then(function(roles) { - ctrl.roles = roles.data; + $q.all([UserService.getAllRoles(), UserService.loadCurrentUser()]).then(function(results) { + ctrl.roles = results[0].data; + ctrl.isAdmin = (results[1].data.role === 'ADMIN'); }); loadUsers(); }; @@ -116,5 +120,47 @@ } }); } + + function revealSystemApiKey() { + UserService.retrieveSystemApiKey().then(function(result) { + ctrl.systemApiKey = result.data; + }); + } + + function rotateSystemApiKey() { + + $uibModal.open({ + size: 'lg', + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/js/admin/feature/users/confirm-api-key-rotation.html', + backdrop: 'static', + controller: function ($scope) { + $scope.cancel = function() { + $scope.$dismiss(); + }; + $scope.confirm = function() { + $scope.$close(true); + } + } + }).result.then(function(result) { + if (result) { + UserService.rotateSystemApiKey().then(function(result) { + ctrl.systemApiKey = result.data; + NotificationHandler.showSuccess('API Key successfully rotated.'); + }); + } + }); + } + + function copySystemApiKey() { + var listener = function(clipboardEvent) { + var clipboard = clipboardEvent.clipboardData || window['clipboardData']; + clipboard.setData('text', ctrl.systemApiKey); + clipboardEvent.preventDefault(); + NotificationHandler.showSuccess('System API Key copied to Clipboard!'); + }; + document.addEventListener('copy', listener, false); + document.execCommand('copy'); + document.removeEventListener('copy', listener, false); + } } })(); \ No newline at end of file diff --git a/src/main/webapp/resources/js/admin/ng-app/admin-application.js b/src/main/webapp/resources/js/admin/ng-app/admin-application.js index bc7533af92..9aa7d0864b 100644 --- a/src/main/webapp/resources/js/admin/ng-app/admin-application.js +++ b/src/main/webapp/resources/js/admin/ng-app/admin-application.js @@ -29,6 +29,7 @@ template: ['<div class="container" container-fluid-responsive="">', '<h1>Events</h1>', '<hr />', + '<export-reservations-button></export-reservations-button>', '<active-events-list></active-events-list>', '<expired-events-list></expired-events-list>', '</div>'].join('') @@ -221,6 +222,15 @@ }, controllerAs: 'ctrl' }) + .state('events.single.paymentsList', { + url: '/transactions/?search', + template: '<payments-list purchase-context="ctrl.event" purchase-context-type="ctrl.purchaseContextType"></payments-list>', + controller: function(getEvent) { + this.event = getEvent.data.event; + this.purchaseContextType = 'event'; + }, + controllerAs: 'ctrl' + }) .state('events.single.ticketsList', { url: '/category/:categoryId/tickets', template: '<tickets-list event="$ctrl.event" category-id="$ctrl.categoryId"></tickets-list>', @@ -940,6 +950,9 @@ $scope.event.vatIncluded = eventToCopy.vatIncluded; $scope.event.allowedPaymentProxies = angular.copy(eventToCopy.allowedPaymentProxies); $scope.event.format = eventToCopy.format; + $scope.event.metadata = { + copiedFrom: eventToCopy.shortName + }; //legacy event, has all the ticket categories with ordinal 0 var isAllOrdinal0 = eventToCopy.ticketCategories.reduce(function(accumulator, tc) {return accumulator && (tc.ordinal === 0);}, true); @@ -960,7 +973,10 @@ tokenGenerationRequested: tc.accessRestricted, code: tc.code, description: tc.description ? angular.copy(tc.description) : null, - ticketAccessType: eventToCopy.format === 'HYBRID' ? tc.ticketAccessType : null + ticketAccessType: eventToCopy.format === 'HYBRID' ? tc.ticketAccessType : null, + metadata: { + copiedFrom: tc.id + } }; if (tc.formattedValidCheckInFrom) { @@ -1149,8 +1165,9 @@ return token.status === 'WAITING'; }; - $scope.canBeDeleted = function(category) { - return category.checkedInTickets + category.soldTickets + category.pendingTickets === 0 + $scope.canBeDeleted = function(event, category) { + return event.ticketCategories.length > 1 + && category.checkedInTickets + category.soldTickets + category.pendingTickets === 0 }; $scope.deleteCategory = function(category, event) { @@ -2046,11 +2063,14 @@ $scope.registerPayment = function(eventName, id) { $scope.loading = true; - EventService.registerPayment(eventName, id).success(function() { - getPendingPayments(true); - }).error(function() { - $scope.loading = false; - }); + EventService.registerPayment(eventName, id).then( + function() { + getPendingPayments(true); + }, + function() { + $scope.loading = false; + } + ); }; $scope.showTransactionDialog = function(pendingPaymentDescriptor) { @@ -2090,7 +2110,6 @@ }; $scope.deletePayment = function(eventName, id, credit) { - var action = credit ? "credit" : "cancel"; var confirmPromise = $uibModal.open({ size:'md', templateUrl:BASE_STATIC_URL + '/pending-payments/delete-or-credit-modal.html', @@ -2099,18 +2118,19 @@ var ctrl = this; ctrl.credit = credit; ctrl.reservationId = id; + ctrl.notify = !credit; ctrl.cancel = function() { $scope.$dismiss('canceled'); }; ctrl.confirm = function() { - $scope.$close(true); + $scope.$close(ctrl.notify); }; }, controllerAs: '$ctrl' }).result; - confirmPromise.then(function() { - return EventService.cancelPayment(eventName, id, credit); + confirmPromise.then(function(notify) { + return EventService.cancelPayment(eventName, id, credit, notify); }).then(function() { getPendingPayments(true); }, function() { diff --git a/src/main/webapp/resources/js/admin/service/service.js b/src/main/webapp/resources/js/admin/service/service.js index 8f060cfe7e..7fd2cdf0f2 100644 --- a/src/main/webapp/resources/js/admin/service/service.js +++ b/src/main/webapp/resources/js/admin/service/service.js @@ -34,7 +34,7 @@ }; }); - baseServices.service('PurchaseContextService', function(EventService, SubscriptionService) { + baseServices.service('PurchaseContextService', function(EventService, SubscriptionService, AdminReservationService, $http, HttpErrorHandler) { return { findAllReservations: function(type, contextName, page, search, status) { if(type === 'event') { @@ -42,9 +42,19 @@ } else { return SubscriptionService.findAllReservations(contextName, page, search, status); } + }, + findAllPayments: function(type, contextName, page, search) { + return $http.get('/admin/api/payments/'+ type + '/' + contextName + '/list', {params: {page: page, search: search}}); + }, + editPaymentDetails: function(reservationId, purchaseContextType, publicIdentifier) { + var infoLoader = AdminReservationService.paymentInfo(purchaseContextType, publicIdentifier, reservationId); + return EventService.editTransactionModal(reservationId, 'edit', infoLoader, function(res) { + return $http.put('/admin/api/payments/'+ purchaseContextType + '/' + publicIdentifier + '/reservation/' + reservationId, res) + .error(HttpErrorHandler.handle); + }); } }; - }) + }); baseServices.service('EventService', function($http, HttpErrorHandler, $uibModal, $window, $rootScope, $q, LocationService, $timeout) { @@ -56,6 +66,9 @@ var service = { data: {}, + getEventsCount: function () { + return $http.get('/admin/api/events-count').error(HttpErrorHandler.handle); + }, getAllEvents : function() { return $http.get('/admin/api/events').error(HttpErrorHandler.handle); }, @@ -170,13 +183,81 @@ getPendingPaymentsCount: function(eventName) { return $http.get('/admin/api/events/'+eventName+'/pending-payments-count').error(HttpErrorHandler.handle).then(function(res) {var v = parseInt(res.data); return isNaN(v) ? 0 : v; }); }, + editTransactionModal: function(reservationId, type, transactionLoader, callback) { + var preloadPromise; + if (transactionLoader) { + preloadPromise = transactionLoader.then(function(container) { + var transaction = container.data.data.transaction; + return { + timestamp: { + date: moment(transaction.timestamp).format('YYYY-MM-DD'), + time: moment(transaction.timestamp).format('HH:mm') + }, + timestampEditable: transaction.timestampEditable, + notes: transaction.notes + } + }); + } else { + preloadPromise = $q.resolve({ + timestamp: { + date: moment().format('YYYY-MM-DD'), + time: moment().format('HH:mm') + }, + timestampEditable: true, + notes: '' + }); + } + + var modal = $uibModal.open({ + size:'md', + templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/angular-templates/admin/partials/payment/edit-transaction-modal.html', + backdrop: 'static', + controllerAs: '$ctrl', + controller: function() { + var ctrl = this; + ctrl.actionText = type === 'pending-payment' ? 'Confirm' : 'Edit'; + ctrl.confirmText = type === 'pending-payment' ? 'Confirm' : 'Save'; + ctrl.reservationId = reservationId; + ctrl.minDate = moment().subtract(1, 'years').format('YYYY-MM-DD'); + ctrl.isLoading = true; + preloadPromise.then(function(res) { + ctrl.isLoading = false; + ctrl.transaction = res; + }, function(err) { + modal.dismiss('error'); + }); + + ctrl.cancel = function() { + modal.dismiss('cancelled'); + }; + ctrl.confirm = function() { + var result = { + timestamp: ctrl.transaction.timestampEditable ? ctrl.transaction.timestamp : null, + notes: ctrl.transaction.notes + }; + if (callback) { + callback(result).then(function() { + modal.close(result); + }); + } else { + modal.close(result); + } + } + } + }); + return modal.result; + }, registerPayment: function(eventName, reservationId) { - return $http['post']('/admin/api/events/'+eventName+'/pending-payments/'+reservationId+'/confirm').error(HttpErrorHandler.handle); + return service.editTransactionModal(reservationId, 'pending-payment').then(function(metadata) { + return $http['post']('/admin/api/events/'+eventName+'/pending-payments/'+reservationId+'/confirm', metadata) + .error(HttpErrorHandler.handle); + }); }, - cancelPayment: function(eventName, reservationId, credit) { + cancelPayment: function(eventName, reservationId, credit, notify) { return $http['delete']('/admin/api/events/'+eventName+'/pending-payments/'+reservationId, { params: { - credit: credit + credit: credit, + notify: notify } }).error(HttpErrorHandler.handle); }, @@ -312,7 +393,7 @@ var queryString = "format="+$scope.format+"&"; angular.forEach($scope.selected, function(v,k) { if(v) { - queryString+="fields="+k+"&"; + queryString+="fields="+ encodeURIComponent(k) +"&"; } }); var pathName = $window.location.pathname; @@ -338,7 +419,7 @@ $scope.reservationId = reservationId; $scope.invoiceRequested = invoiceRequested; $scope.close = function() { - $scope.$close(false); + $scope.$dismiss(false); }; $scope.success = function (result) { @@ -621,20 +702,26 @@ }; }]); - baseServices.service("NotificationHandler", ["growl", function (growl) { + baseServices.service("NotificationHandler", ["growl", "$sanitize", function (growl, $sanitize) { var config = {ttl: 5000, disableCountDown: true}; + var sanitize = function(message) { + var sanitized = $sanitize(message); + return sanitized.split(' ').map(function(part) { + return encodeURIComponent(part); + }).join(' '); + }; return { showSuccess: function (message) { - return growl.success(message, config); + return growl.success(sanitize(message), config); }, showWarning: function (message) { - return growl.warning(message, config); + return growl.warning(sanitize(message), config); }, showInfo : function (message) { - return growl.info(message, config); + return growl.info(sanitize(message), config); }, showError : function (message) { - return growl.error(message, config); + return growl.error(sanitize(message), config); } } @@ -718,6 +805,13 @@ update: function(promoCodeId, toUpdate) { addUtfOffsetIfNecessary(toUpdate); return $http.post('/admin/api/promo-code/' + promoCodeId, toUpdate); + }, + getUsageDetails: function(promoCodeId, eventShortName) { + return $http.get('/admin/api/promo-code/' + promoCodeId + '/detailed-usage', { + params: { + eventShortName + } + }); } }; }); @@ -980,8 +1074,8 @@ deferred.reject('Your image was not uploaded correctly.Please upload the image again'); } else if (!((files[0].type === 'image/png') || (files[0].type === 'image/jpeg') || (files[0].type === 'image/gif') || (files[0].type === 'image/svg+xml'))) { deferred.reject('Only PNG, JPG, GIF or SVG image files are accepted'); - } else if (files[0].size > (1024 * 200)) { - deferred.reject('Image size exceeds the allowable limit 200KB'); + } else if (files[0].size > (1024 * 1024)) { + deferred.reject('Image is too big'); } else { reader.readAsDataURL(files[0]); } diff --git a/src/main/webapp/resources/js/admin/service/user.service.js b/src/main/webapp/resources/js/admin/service/user.service.js index ea60572be7..246bc3f006 100644 --- a/src/main/webapp/resources/js/admin/service/user.service.js +++ b/src/main/webapp/resources/js/admin/service/user.service.js @@ -43,6 +43,12 @@ resetPassword: function(user) { return $http['put']('/admin/api/users/'+user.id+'/reset-password?baseUrl='+window.encodeURIComponent($window.location.origin)).error(HttpErrorHandler.handle); }, + retrieveSystemApiKey: function() { + return $http.get('/admin/api/system/api-key').error(HttpErrorHandler.handle); + }, + rotateSystemApiKey: function() { + return $http['put']('/admin/api/system/api-key').error(HttpErrorHandler.handle); + }, showUserData: function(user) { return $uibModal.open({ diff --git a/src/test/java/alfio/BaseTestConfiguration.java b/src/test/java/alfio/BaseTestConfiguration.java index d8b4ffd38e..08f85215e4 100644 --- a/src/test/java/alfio/BaseTestConfiguration.java +++ b/src/test/java/alfio/BaseTestConfiguration.java @@ -21,19 +21,24 @@ import alfio.manager.FileDownloadManager; import alfio.manager.system.ExternalConfiguration; import alfio.model.system.ConfigurationKeys; -import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; +import alfio.util.RefreshableDataSource; +import com.stripe.Stripe; +import com.fasterxml.jackson.databind.ObjectMapper; import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; +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.Profile; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.ByteArrayResource; +import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; -import javax.sql.DataSource; +import javax.annotation.PostConstruct; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.net.http.HttpClient; @@ -41,6 +46,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -50,6 +56,9 @@ @Configuration(proxyBeanMethods = false) public class BaseTestConfiguration { + public static final int MAX_POOL_SIZE = 5; + private static final Logger log = LoggerFactory.getLogger(BaseTestConfiguration.class); + @Bean @Profile("!travis") public PlatformProvider getCloudProvider() { @@ -58,9 +67,12 @@ public PlatformProvider getCloudProvider() { @Bean @Profile("!travis") - public DataSource getDataSource() { + public RefreshableDataSource dataSource() { String POSTGRES_DB = "alfio"; - PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14") + String postgresVersion = Objects.requireNonNullElse(System.getProperty("pgsql.version"), "10"); + log.debug("Running tests using PostgreSQL v.{}", postgresVersion); + PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(DockerImageName.parse("docker.io/postgres:"+postgresVersion) + .asCompatibleSubstituteFor("postgres")) .withDatabaseName(POSTGRES_DB) .withInitScript("init-db-user.sql"); postgres.start(); @@ -69,8 +81,8 @@ public DataSource getDataSource() { config.setUsername("alfio_user"); config.setPassword("password"); config.setDriverClassName(postgres.getDriverClassName()); - config.setMaximumPoolSize(5); - return new HikariDataSource(config); + config.setMaximumPoolSize(MAX_POOL_SIZE); + return new RefreshableDataSource(config); } @Bean @@ -116,4 +128,20 @@ public ExternalConfiguration externalConfiguration() { public ClockProvider clockProvider() { return FIXED_TIME_CLOCK; } + + @PostConstruct + public void initStripeMock() { + GenericContainer<?> stripeMock = new GenericContainer<>("stripe/stripe-mock:latest") + .withExposedPorts(12111, 12112); + stripeMock.start(); + var httpPort = stripeMock.getMappedPort(12111); + Stripe.overrideApiBase("http://localhost:" + httpPort); + Stripe.overrideUploadBase("http://localhost:" + httpPort); + Stripe.enableTelemetry = false; + } + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } } diff --git a/src/test/java/alfio/TestConfiguration.java b/src/test/java/alfio/TestConfiguration.java index 8ece4d50e4..ac8089c2f3 100644 --- a/src/test/java/alfio/TestConfiguration.java +++ b/src/test/java/alfio/TestConfiguration.java @@ -16,18 +16,24 @@ */ package alfio; +import alfio.config.BaseConfiguration; import alfio.manager.system.ConfigurationManager; import alfio.manager.system.ExternalConfiguration; import alfio.manager.user.UserManager; import alfio.model.system.ConfigurationKeys; import alfio.repository.EventRepository; import alfio.repository.system.ConfigurationRepository; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.session.FindByIndexNameSessionRepository; import java.time.Duration; import java.util.Map; @@ -54,4 +60,14 @@ ConfigurationManager configurationManager(ConfigurationRepository configurationR environment, cache); } + + @Bean + CsrfTokenRepository csrfTokenRepository() { + return new CookieCsrfTokenRepository(); + } + + @Bean + FindByIndexNameSessionRepository<?> sessionsByPrincipalFinder() { + return Mockito.mock(FindByIndexNameSessionRepository.class); + } } diff --git a/src/test/java/alfio/controller/IndexControllerTest.java b/src/test/java/alfio/controller/IndexControllerTest.java new file mode 100644 index 0000000000..59eb038350 --- /dev/null +++ b/src/test/java/alfio/controller/IndexControllerTest.java @@ -0,0 +1,132 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller; + +import alfio.controller.api.v2.model.EventWithAdditionalInfo; +import alfio.controller.api.v2.model.Language; +import alfio.controller.api.v2.user.support.EventLoader; +import alfio.manager.i18n.MessageSourceManager; +import alfio.util.Json; +import ch.digitalfondue.jfiveparse.Element; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.ServletWebRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +class IndexControllerTest { + + private EventLoader eventLoader; + private Element head; + private Element index; + private Element html; + private EventWithAdditionalInfo event; + private HttpSession session; + + private ServletWebRequest request; + private MessageSourceManager messageSourceManager; + private Json json; + + @BeforeEach + void setUp() { + eventLoader = mock(EventLoader.class); + request = mock(ServletWebRequest.class); + head = mock(Element.class); + index = mock(Element.class); + html = mock(Element.class); + event = mock(EventWithAdditionalInfo.class); + session = mock(HttpSession.class); + json = mock(Json.class); + messageSourceManager = mock(MessageSourceManager.class); + when(messageSourceManager.getBundleAsMap(anyString(), anyBoolean(), anyString(), same(MessageSourceManager.PUBLIC_FRONTEND))).thenReturn(Map.of()); + when(eventLoader.loadEventInfo(anyString(), eq(session))).thenReturn(Optional.of(event)); + when(index.getElementsByTagName("html")).thenReturn(List.of(html)); + when(json.asJsonString(any())).thenReturn("{}"); + when(request.getNativeRequest(HttpServletRequest.class)).thenReturn(new MockHttpServletRequest()); + } + + @Nested + @DisplayName("Event is present") + class EventIsPresent { + @Test + void singleLanguage() { + when(event.getContentLanguages()).thenReturn(List.of(new Language("it", ""))); + IndexController.preloadTranslations("shortName", request, session, eventLoader, head, messageSourceManager, index, json, null); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("it"), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", "it"); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("en"), same(MessageSourceManager.PUBLIC_FRONTEND)); //for non en language we preload also the fallback + } + + @Test + void singleLanguageWithWrongParam() { + when(event.getContentLanguages()).thenReturn(List.of(new Language("it", ""))); + IndexController.preloadTranslations("shortName", request, session, eventLoader, head, messageSourceManager, index, json, "de"); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("it"), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", "it"); + } + + @Test + void singleLanguageWithParam() { + when(event.getContentLanguages()).thenReturn(List.of(new Language("de", ""))); + IndexController.preloadTranslations("shortName", request, session, eventLoader, head, messageSourceManager, index, json, "de"); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("de"), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", "de"); + } + + @Test + void multipleLanguages() { + when(event.getContentLanguages()).thenReturn(List.of(new Language("de", ""), new Language("it", ""))); + IndexController.preloadTranslations("shortName", request, session, eventLoader, head, messageSourceManager, index, json, null); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("de"), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", "de"); + } + + @ParameterizedTest + @ValueSource(strings = {"it", "de"}) + void multipleLanguagesWithParam(String param) { + when(event.getContentLanguages()).thenReturn(List.of(new Language("de", ""), new Language("it", ""))); + IndexController.preloadTranslations("shortName", request, session, eventLoader, head, messageSourceManager, index, json, param); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq(param), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", param); + } + + + } + + @Test + void preloadTranslationsEventNotPresent() { + IndexController.preloadTranslations(null, request, session, eventLoader, head, messageSourceManager, index, json, null); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("en"), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", "en"); + + IndexController.preloadTranslations(null, request, session, eventLoader, head, messageSourceManager, index, json, "it"); + verify(messageSourceManager).getBundleAsMap(anyString(), eq(true), eq("it"), same(MessageSourceManager.PUBLIC_FRONTEND)); + verify(html).setAttribute("lang", "it"); + } +} \ No newline at end of file diff --git a/src/test/java/alfio/controller/api/CheckJsonBodyRequest.java b/src/test/java/alfio/controller/api/CheckJsonBodyRequest.java new file mode 100644 index 0000000000..5e7748f3b7 --- /dev/null +++ b/src/test/java/alfio/controller/api/CheckJsonBodyRequest.java @@ -0,0 +1,80 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.ParameterizedType; +import java.util.*; + +@SpringBootTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +public class CheckJsonBodyRequest { + + @Autowired + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + @Autowired + private ObjectMapper objectMapper; + + /** + * Check that all our classes are annotated/formed correctly so we can + * Deserialize from json to java object. + */ + @Test + void checkSerialization() { + var om = objectMapper; + var handlerMethods = this.requestMappingHandlerMapping.getHandlerMethods(); + var classesToCheck = new HashSet<Class<?>>(); + for (var item : handlerMethods.entrySet()) { + var method = item.getValue(); + Arrays + .stream(method.getMethodParameters()) + .filter(p -> p.hasParameterAnnotation(RequestBody.class)) + .forEach(p -> { + var k = p.getGenericParameterType(); + if (k instanceof ParameterizedType pt) { + Arrays + .stream(pt.getActualTypeArguments()) + .filter(g -> g.getTypeName().startsWith("alfio.")) // keep only our types + .filter(g -> !((Class) g).isEnum()) // filter out enums + .forEach(genericParamType -> classesToCheck.add((Class) genericParamType)); + } else if (p.getParameterType().getTypeName().startsWith("alfio.") && !p.getParameterType().isEnum()){ + classesToCheck.add(p.getParameterType()); + } + }); + } + classesToCheck.forEach(k -> { + Assertions.assertDoesNotThrow(() -> { + om.readValue("{}", k); + }, "Was not able to deserialize value of type " + k); + }); + } + +} diff --git a/src/test/java/alfio/controller/api/CheckRestApiStability.java b/src/test/java/alfio/controller/api/CheckRestApiStability.java new file mode 100644 index 0000000000..54c17288bd --- /dev/null +++ b/src/test/java/alfio/controller/api/CheckRestApiStability.java @@ -0,0 +1,110 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openapitools.openapidiff.core.OpenApiCompare; +import org.openapitools.openapidiff.core.output.MarkdownRender; +import org.springdoc.core.Constants; +import org.springdoc.core.SpringDocConfigProperties; +import org.springdoc.core.SpringDocConfiguration; +import org.springdoc.webmvc.core.SpringDocWebMvcConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import java.io.FileReader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@AutoConfigureMockMvc +@ContextConfiguration(classes = { + DataSourceConfiguration.class, + TestConfiguration.class, + ControllerConfiguration.class, + TestCheckRestApiStability.DisableSecurity.class, + SpringDocConfiguration.class, + SpringDocConfigProperties.class, + SpringDocWebMvcConfiguration.class +}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class TestCheckRestApiStability { + + private static final String DESCRIPTOR_JSON_PATH = "src/test/resources/api/descriptor.json"; + @Autowired + private MockMvc mockMvc; + + private final boolean updateDescriptor = false; // change to true to regenerate the file + + @Test + void checkRestApiStability() throws Exception { + + var mvcResult = this.mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL)) + .andExpect(status().isOk()) + .andReturn(); + + var response = mvcResult.getResponse(); + var descriptor = response.getContentAsString(); + + // for generating the result + if (updateDescriptor) { + try (var writer = Files.newBufferedWriter(Paths.get(DESCRIPTOR_JSON_PATH), StandardCharsets.UTF_8)) { + var formattedDescriptor = new ObjectMapper().readTree(descriptor).toPrettyString(); + writer.write(formattedDescriptor); + } + } + + var referenceDescriptor = IOUtils.toString(new FileReader(DESCRIPTOR_JSON_PATH)); + var currentDescriptor = IOUtils.toString(new StringReader(descriptor)); + var compareResult = OpenApiCompare.fromContents(referenceDescriptor, currentDescriptor); + if (compareResult.isDifferent()) { + Assertions.fail(new MarkdownRender().render(compareResult)); + } + } + + + @EnableWebSecurity + @Configuration + public static class DisableSecurity { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.authorizeRequests((auth) -> auth.antMatchers("/**").permitAll()).build(); + } + } +} diff --git a/src/test/java/alfio/controller/api/ControllerConfiguration.java b/src/test/java/alfio/controller/api/ControllerConfiguration.java index 6124c157eb..3e0ed7420c 100644 --- a/src/test/java/alfio/controller/api/ControllerConfiguration.java +++ b/src/test/java/alfio/controller/api/ControllerConfiguration.java @@ -16,11 +16,82 @@ */ package alfio.controller.api; +import alfio.controller.IndexController; +import alfio.controller.api.v2.user.support.EventLoader; +import alfio.controller.support.CSPConfigurer; +import alfio.manager.PurchaseContextManager; +import alfio.manager.i18n.MessageSourceManager; +import alfio.manager.system.ConfigurationManager; +import alfio.repository.*; +import alfio.repository.user.OrganizationRepository; +import alfio.util.Json; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.web.csrf.CsrfTokenRepository; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Collections; +import java.util.List; @Configuration(proxyBeanMethods = false) @ComponentScan(basePackages = {"alfio.controller"}) -public class ControllerConfiguration { +@EnableWebMvc +public class ControllerConfiguration implements WebMvcConfigurer { + + + @Autowired + private ObjectMapper objectMapper; + + private MappingJackson2HttpMessageConverter jacksonMessageConverter(ObjectMapper objectMapper) { + final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(objectMapper); + return converter; + } + + @Override + public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { + // see https://github.com/springdoc/springdoc-openapi/issues/624#issuecomment-633155765 + StringHttpMessageConverter converter = new StringHttpMessageConverter(); + converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL)); + converters.add(converter); + // + converters.add(jacksonMessageConverter(objectMapper)); + } + @Bean + public IndexController indexController(ConfigurationManager configurationManager, + EventRepository eventRepository, + FileUploadRepository fileUploadRepository, + MessageSourceManager messageSourceManager, + EventDescriptionRepository eventDescriptionRepository, + OrganizationRepository organizationRepository, + TicketReservationRepository ticketReservationRepository, + SubscriptionRepository subscriptionRepository, + EventLoader eventLoader, + PurchaseContextManager purchaseContextManager, + CsrfTokenRepository csrfTokenRepository, + CSPConfigurer cspConfigurer, + Json json) { + return new IndexController(configurationManager, + eventRepository, + fileUploadRepository, + messageSourceManager, + eventDescriptionRepository, + organizationRepository, + ticketReservationRepository, + subscriptionRepository, + eventLoader, + purchaseContextManager, + csrfTokenRepository, + cspConfigurer, + json); + } } diff --git a/src/test/java/alfio/controller/api/admin/EventApiControllerIntegrationTest.java b/src/test/java/alfio/controller/api/admin/EventApiControllerIntegrationTest.java new file mode 100644 index 0000000000..8c250ae5f2 --- /dev/null +++ b/src/test/java/alfio/controller/api/admin/EventApiControllerIntegrationTest.java @@ -0,0 +1,140 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.admin; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import alfio.controller.api.ControllerConfiguration; +import alfio.manager.EventManager; +import alfio.manager.user.UserManager; +import alfio.model.Event; +import alfio.model.TicketCategory; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.modification.DateTimeModification; +import alfio.model.modification.TicketCategoryModification; +import alfio.repository.EventDeleterRepository; +import alfio.repository.EventRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; +import alfio.test.util.IntegrationTestUtil; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.security.Principal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import static alfio.test.util.IntegrationTestUtil.*; +import static alfio.test.util.TestUtil.clockProvider; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@AlfioIntegrationTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class EventApiControllerIntegrationTest { + + @Autowired + private ConfigurationRepository configurationRepository; + @Autowired + private OrganizationRepository organizationRepository; + @Autowired + private UserManager userManager; + @Autowired + private EventManager eventManager; + @Autowired + private EventRepository eventRepository; + @Autowired + private EventDeleterRepository eventDeleterRepository; + @Autowired + private EventApiController eventApiController; + + private Event event; + + @Test + void getAllEventsForExternalInPerson() { + var eventAndUser = createEvent(Event.EventFormat.IN_PERSON); + event = eventAndUser.getKey(); + var principal = Mockito.mock(Principal.class); + when(principal.getName()).thenReturn(eventAndUser.getValue()); + var events = eventApiController.getAllEventsForExternal(principal, new MockHttpServletRequest(), false); + + assertNotNull(events); + assertEquals(1, events.size()); + assertEquals(event.getShortName(), events.get(0).getKey()); + } + + @Test + void getAllEventsForExternalHybrid() { + var eventAndUser = createEvent(Event.EventFormat.HYBRID); + event = eventAndUser.getKey(); + var principal = Mockito.mock(Principal.class); + when(principal.getName()).thenReturn(eventAndUser.getValue()); + var events = eventApiController.getAllEventsForExternal(principal, new MockHttpServletRequest(), false); + + assertNotNull(events); + assertEquals(1, events.size()); + assertEquals(event.getShortName(), events.get(0).getKey()); + } + + @Test + void getAllEventsForExternalOnline() { + var eventAndUser = createEvent(Event.EventFormat.ONLINE); + event = eventAndUser.getKey(); + var principal = Mockito.mock(Principal.class); + when(principal.getName()).thenReturn(eventAndUser.getValue()); + var events = eventApiController.getAllEventsForExternal(principal, new MockHttpServletRequest(), false); + + assertNotNull(events); + assertEquals(0, events.size()); + + events = eventApiController.getAllEventsForExternal(principal, new MockHttpServletRequest(), true); + assertNotNull(events); + assertEquals(1, events.size()); + assertEquals(event.getShortName(), events.get(0).getKey()); + } + + private Pair<Event,String> createEvent(Event.EventFormat format) { + IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); + List<TicketCategoryModification> categories = List.of( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new DateTimeModification(LocalDate.now(clockProvider().getClock()).minusDays(1), LocalTime.now(clockProvider().getClock())), + new DateTimeModification(LocalDate.now(clockProvider().getClock()).plusDays(1), LocalTime.now(clockProvider().getClock())), + DESCRIPTION, BigDecimal.ZERO, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()) + ); + return initEvent(categories, organizationRepository, userManager, eventManager, eventRepository, List.of(), format); + + } + + @AfterEach + void tearDown() { + eventDeleterRepository.deleteAllForEvent(event.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/alfio/controller/api/admin/PollAdminApiControllerTest.java b/src/test/java/alfio/controller/api/admin/PollAdminApiControllerTest.java index 9b9bffd467..d804d38a06 100644 --- a/src/test/java/alfio/controller/api/admin/PollAdminApiControllerTest.java +++ b/src/test/java/alfio/controller/api/admin/PollAdminApiControllerTest.java @@ -32,6 +32,7 @@ import alfio.repository.*; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.time.DateUtils; @@ -55,10 +56,9 @@ import static alfio.test.util.TestUtil.clockProvider; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class PollAdminApiControllerTest { @Autowired @@ -168,7 +168,7 @@ void allowPeopleToVote() { assertTrue(res.getStatusCode().is2xxSuccessful()); assertTrue(CollectionUtils.isNotEmpty(res.getBody())); assertEquals(1, res.getBody().size()); - assertEquals(firstTicket.getId(), res.getBody().get(0).getId()); + assertEquals(firstTicket.getId(), res.getBody().get(0).id()); // allow tickets to vote var poll = pollRepository.findSingleForEvent(event.getId(), pollId).orElseThrow(); @@ -183,7 +183,7 @@ void allowPeopleToVote() { assertTrue(participantRes.getStatusCode().is2xxSuccessful()); assertTrue(CollectionUtils.isNotEmpty(participantRes.getBody())); assertEquals(1, participantRes.getBody().size()); - assertEquals(firstTicket.getId(), participantRes.getBody().get(0).getId()); + assertEquals(firstTicket.getId(), participantRes.getBody().get(0).id()); // now ticket should not be returned anymore res = controller.findAdditionalAttendees(event.getShortName(), pollId, "First"); diff --git a/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java b/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java index 77b1992eaf..2c3ae41bc7 100644 --- a/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java +++ b/src/test/java/alfio/controller/api/v1/EventApiV1IntegrationTest.java @@ -25,6 +25,7 @@ import alfio.manager.user.UserManager; import alfio.model.Event; import alfio.model.TicketCategory; +import alfio.model.TicketCategoryWithAdditionalInfo; import alfio.model.api.v1.admin.EventCreationRequest; import alfio.model.modification.OrganizationModification; import alfio.model.transaction.PaymentProxy; @@ -34,9 +35,10 @@ import alfio.repository.TicketCategoryRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; -import org.junit.jupiter.api.Assertions; +import alfio.util.ClockProvider; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -54,15 +56,14 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class EventApiV1IntegrationTest extends BaseIntegrationTest { @BeforeAll @@ -94,7 +95,7 @@ public static void initEnv() { private Organization organization; private Principal mockPrincipal; - private String shortName = "test"; + private final String slug = "test"; @BeforeEach public void ensureConfiguration() { @@ -104,9 +105,9 @@ public void ensureConfiguration() { this.username = UUID.randomUUID().toString(); var organizationModification = new OrganizationModification(null, organizationName, "email@example.com", "org", null, null); - userManager.createOrganization(organizationModification); + userManager.createOrganization(organizationModification, null); this.organization = organizationRepository.findByName(organizationName).orElseThrow(); - userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.API_CONSUMER, User.Type.INTERNAL); + userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.API_CONSUMER, User.Type.INTERNAL, null); this.mockPrincipal = Mockito.mock(Principal.class); Mockito.when(mockPrincipal.getName()).thenReturn(username); @@ -114,10 +115,10 @@ public void ensureConfiguration() { } - private EventCreationRequest creationRequest() { + static EventCreationRequest creationRequest(String shortName) { return new EventCreationRequest( "Title", - this.shortName, + shortName, Collections.singletonList(new EventCreationRequest.DescriptionRequest("en", "desc")), null, new EventCreationRequest.LocationRequest( @@ -125,12 +126,12 @@ private EventCreationRequest creationRequest() { new EventCreationRequest.CoordinateRequest("45.5","9.00") ), "Europe/Zurich", - LocalDateTime.of(2020,1,10,12,00), - LocalDateTime.of(2020,1,10,18,00), + LocalDateTime.now(ClockProvider.clock()).plusDays(30), + LocalDateTime.now(ClockProvider.clock()).plusDays(30).plusHours(2), "https://alf.io", "https://alf.io", "https://alf.io", - "https://avatars3.githubusercontent.com/u/34451076", + "https://alf.io/img/tutorials/check-in-app/003.png", new EventCreationRequest.TicketRequest( false, 10, @@ -140,6 +141,7 @@ private EventCreationRequest creationRequest() { Arrays.asList(PaymentProxy.OFFLINE,PaymentProxy.STRIPE), Collections.singletonList( new EventCreationRequest.CategoryRequest( + null, // forces new category "standard", Collections.singletonList(new EventCreationRequest.DescriptionRequest("en", "desc")), 10, @@ -164,49 +166,177 @@ private EventCreationRequest creationRequest() { @Test - public void createTest() { + void createTest() { - EventCreationRequest eventCreationRequest = creationRequest(); + EventCreationRequest eventCreationRequest = creationRequest(slug); - String shortName = controller.create(eventCreationRequest,mockPrincipal).getBody(); - Event event = eventManager.getSingleEvent(shortName,username); + String slug = controller.create(eventCreationRequest,mockPrincipal).getBody(); + Event event = eventManager.getSingleEvent(slug,username); List<TicketCategory> tickets = ticketCategoryRepository.findAllTicketCategories(event.getId()); - - - assertEquals(eventCreationRequest.getTitle(),event.getDisplayName()); assertEquals(eventCreationRequest.getSlug(),event.getShortName()); assertEquals(eventCreationRequest.getTickets().getCurrency(),event.getCurrency()); assertEquals(eventCreationRequest.getWebsiteUrl(),event.getWebsiteUrl()); assertEquals(eventCreationRequest.getTickets().getPaymentMethods(),event.getAllowedPaymentProxies()); - Assertions.assertTrue(event.getFileBlobIdIsPresent()); + assertTrue(event.getFileBlobIdIsPresent()); assertEquals(eventCreationRequest.getTickets().getCategories().size(),tickets.size()); tickets.forEach((t) -> { - List<EventCreationRequest.CategoryRequest> requestCategories = eventCreationRequest.getTickets().getCategories().stream().filter((rt) -> rt.getName().equals(t.getName())).collect(Collectors.toList()); + List<EventCreationRequest.CategoryRequest> requestCategories = eventCreationRequest.getTickets().getCategories().stream().filter((rt) -> rt.getName().equals(t.getName())).toList(); assertEquals(1,requestCategories.size()); requestCategories.forEach((rtc) -> { + assertNotEquals(0, t.getOrdinal()); assertEquals(t.getMaxTickets(), rtc.getMaxTickets().intValue()); assertEquals(0, t.getPrice().compareTo(rtc.getPrice())); } ); } ); - } @Test - public void updateTest() { - controller.create(creationRequest(),mockPrincipal); - + void stats() { + controller.create(creationRequest(slug), mockPrincipal); + var statsResponse = controller.stats(slug, mockPrincipal); + assertTrue(statsResponse.getStatusCode().is2xxSuccessful()); + var stats = requireNonNull(statsResponse.getBody()); + int lastOrdinal = -1; + for (TicketCategoryWithAdditionalInfo ticketCategory : stats.getTicketCategories()) { + assertTrue(ticketCategory.getOrdinal() > 0); + assertTrue(ticketCategory.getOrdinal() > lastOrdinal); + lastOrdinal = ticketCategory.getOrdinal(); + } + } + @Test + void updateTest() { + controller.create(creationRequest(slug), mockPrincipal); String newTitle = "new title"; EventCreationRequest updateRequest = new EventCreationRequest(newTitle,null,null,null, null,null,null,null,null,null, null,null, new EventCreationRequest.TicketRequest(null,10,null,null,null,null,null,null), null, null ); - controller.update(shortName,updateRequest,mockPrincipal); - Event event = eventManager.getSingleEvent(shortName,username); + controller.update(slug, updateRequest, mockPrincipal); + Event event = eventManager.getSingleEvent(slug,username); assertEquals(newTitle,event.getDisplayName()); + } + + @Test + void updateExistingCategoryUsingId() { + controller.create(creationRequest(slug), mockPrincipal); + var existing = requireNonNull(controller.stats(slug, mockPrincipal).getBody()); + var existingCategory = existing.getTicketCategories().get(0); + var categoriesRequest = List.of( + new EventCreationRequest.CategoryRequest( + existingCategory.getId(), + existingCategory.getName() + "_1", + List.of(new EventCreationRequest.DescriptionRequest("en", "desc")), + existingCategory.getMaxTickets(), + existingCategory.isAccessRestricted(), + existingCategory.getPrice(), + LocalDateTime.now(ClockProvider.clock()), + LocalDateTime.now(ClockProvider.clock()).plusHours(1), + existingCategory.getCode(), + null, + null, + existingCategory.getTicketAccessType() + ) + ); + var ticketRequest = new EventCreationRequest.TicketRequest(null,10,null,null,null,null, categoriesRequest,null); + EventCreationRequest updateRequest = new EventCreationRequest(null,null,null,null, null,null,null,null,null,null, null,null, + ticketRequest, null, null + ); + assertTrue(controller.update(slug, updateRequest, mockPrincipal).getStatusCode().is2xxSuccessful()); + var modifiedCategories = ticketCategoryRepository.findAllTicketCategories(existing.getId()); + assertEquals(1, modifiedCategories.size()); + assertEquals(existingCategory.getName() + "_1", modifiedCategories.get(0).getName()); + } + @Test + void updateExistingCategoryAndAddNewUsingId() { + controller.create(creationRequest(slug), mockPrincipal); + var existing = requireNonNull(controller.stats(slug, mockPrincipal).getBody()); + var existingCategory = existing.getTicketCategories().get(0); + var categoriesRequest = List.of( + new EventCreationRequest.CategoryRequest( + existingCategory.getId(), + existingCategory.getName() + "_1", + List.of(new EventCreationRequest.DescriptionRequest("en", "desc")), + existingCategory.getMaxTickets() - 5, + existingCategory.isAccessRestricted(), + existingCategory.getPrice(), + LocalDateTime.now(ClockProvider.clock()), + LocalDateTime.now(ClockProvider.clock()).plusHours(1), + existingCategory.getCode(), + null, + null, + existingCategory.getTicketAccessType() + ), + new EventCreationRequest.CategoryRequest( + null, + existingCategory.getName() + "_2", + List.of(new EventCreationRequest.DescriptionRequest("en", "desc")), + existingCategory.getMaxTickets() - 5, + existingCategory.isAccessRestricted(), + existingCategory.getPrice(), + LocalDateTime.now(ClockProvider.clock()), + LocalDateTime.now(ClockProvider.clock()).plusHours(1), + existingCategory.getCode(), + null, + null, + existingCategory.getTicketAccessType() + ) + ); + var ticketRequest = new EventCreationRequest.TicketRequest(null,10,null,null,null,null, categoriesRequest,null); + EventCreationRequest updateRequest = new EventCreationRequest(null,null,null,null, null,null,null,null,null,null, null,null, + ticketRequest, null, null + ); + assertTrue(controller.update(slug, updateRequest, mockPrincipal).getStatusCode().is2xxSuccessful()); + var modifiedCategories = ticketCategoryRepository.findAllTicketCategories(existing.getId()); + assertEquals(2, modifiedCategories.size()); + assertEquals(existingCategory.getName() + "_1", modifiedCategories.get(0).getName()); + assertEquals(existingCategory.getOrdinal(), modifiedCategories.get(0).getOrdinal()); + assertEquals(existingCategory.getName() + "_2", modifiedCategories.get(1).getName()); + assertEquals(existingCategory.getOrdinal() + 1, modifiedCategories.get(1).getOrdinal()); + } + + @Test + void updateExistingCategoryUsingName() { + controller.create(creationRequest(slug), mockPrincipal); + var existing = requireNonNull(controller.stats(slug, mockPrincipal).getBody()); + var existingCategory = existing.getTicketCategories().get(0); + var categoriesRequest = List.of( + new EventCreationRequest.CategoryRequest(null, + existingCategory.getName(), + List.of(new EventCreationRequest.DescriptionRequest("en", "desc")), + existingCategory.getMaxTickets() - 1, + existingCategory.isAccessRestricted(), + existingCategory.getPrice(), + LocalDateTime.now(ClockProvider.clock()), + LocalDateTime.now(ClockProvider.clock()).plusHours(1), + existingCategory.getCode(), + null, + null, + existingCategory.getTicketAccessType() + ) + ); + var ticketRequest = new EventCreationRequest.TicketRequest(null,10,null,null,null,null, categoriesRequest,null); + EventCreationRequest updateRequest = new EventCreationRequest(null,null,null,null, null,null,null,null,null,null, null,null, + ticketRequest, null, null + ); + assertTrue(controller.update(slug, updateRequest, mockPrincipal).getStatusCode().is2xxSuccessful()); + var modifiedCategories = ticketCategoryRepository.findAllTicketCategories(existing.getId()); + assertEquals(1, modifiedCategories.size()); + assertEquals(existingCategory.getMaxTickets() - 1, modifiedCategories.get(0).getMaxTickets()); + } + + @Test + void retrieveLinkedSubscriptions() { + controller.create(creationRequest(slug),mockPrincipal); + var response = controller.getLinkedSubscriptions(slug, mockPrincipal); + assertTrue(response.getStatusCode().is2xxSuccessful()); + var linkedSubscriptions = response.getBody(); + assertNotNull(linkedSubscriptions); + assertTrue(linkedSubscriptions.getSubscriptions().isEmpty()); + assertEquals(slug, linkedSubscriptions.getEventSlug()); } diff --git a/src/test/java/alfio/controller/api/v1/ReservationApiV1ControllerTest.java b/src/test/java/alfio/controller/api/v1/ReservationApiV1ControllerTest.java index 4e2fe0355c..f76414e621 100644 --- a/src/test/java/alfio/controller/api/v1/ReservationApiV1ControllerTest.java +++ b/src/test/java/alfio/controller/api/v1/ReservationApiV1ControllerTest.java @@ -22,25 +22,25 @@ import alfio.config.authentication.support.APITokenAuthentication; import alfio.controller.api.ControllerConfiguration; import alfio.controller.api.v1.admin.ReservationApiV1Controller; +import alfio.controller.api.v1.admin.SubscriptionApiV1Controller; import alfio.manager.EventManager; import alfio.manager.user.UserManager; import alfio.model.Event; import alfio.model.TicketCategory; -import alfio.model.api.v1.admin.ReservationCreationRequest; -import alfio.model.api.v1.admin.ReservationUser; +import alfio.model.api.v1.admin.*; import alfio.model.metadata.AlfioMetadata; import alfio.model.metadata.TicketMetadataContainer; +import alfio.model.modification.AttendeeData; import alfio.model.modification.DateTimeModification; import alfio.model.modification.TicketCategoryModification; -import alfio.model.modification.TicketReservationModification; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.system.ConfigurationKeys; import alfio.model.user.Role; import alfio.model.user.User; -import alfio.repository.EventRepository; -import alfio.repository.TicketCategoryRepository; -import alfio.repository.TicketRepository; -import alfio.repository.TicketReservationRepository; +import alfio.repository.*; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.ClockProvider; import org.apache.commons.lang3.StringUtils; @@ -48,27 +48,22 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalTime; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import static alfio.controller.api.v1.SubscriptionApiV1IntegrationTest.modificationRequest; import static alfio.model.system.ConfigurationKeys.OPENID_PUBLIC_ENABLED; import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class ReservationApiV1ControllerTest { private static final String DEFAULT_CATEGORY_NAME = "default"; @@ -94,6 +89,10 @@ class ReservationApiV1ControllerTest { private TicketRepository ticketRepository; @Autowired private TicketReservationRepository ticketReservationRepository; + @Autowired + private SubscriptionApiV1Controller subscriptionApiV1Controller; + @Autowired + private SubscriptionRepository subscriptionRepository; private Event event; private String username; @@ -114,26 +113,25 @@ void setUp() { Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); event = eventAndUser.getLeft(); username = UUID.randomUUID().toString(); - userManager.insertUser(event.getOrganizationId(), username, "test", "test", "test@example.com", Role.API_CONSUMER, User.Type.INTERNAL); + userManager.insertUser(event.getOrganizationId(), username, "test", "test", "test@example.com", Role.API_CONSUMER, User.Type.INTERNAL, null); } @Test void createSingleTicketWithMetadata() { var category = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId()).orElseThrow(); - var ticket = new TicketReservationModification(); - ticket.setQuantity(1); - ticket.setTicketCategoryId(category.getId()); var firstTicketProperties = Map.of("property", "value-first"); - ticket.setMetadata(List.of(firstTicketProperties)); - var creationRequest = new ReservationCreationRequest( + var ticket = new AttendeesByCategory(category.getId(), 1, List.of(), List.of(firstTicketProperties)); + var creationRequest = new TicketReservationCreationRequest( List.of(ticket), List.of(), + new ReservationConfiguration(true, false, true), null, null, - "en" + "en", + null ); var principal = new APITokenAuthentication(username, null, List.of()); - var response = controller.createReservation(event.getShortName(), creationRequest, principal); + var response = controller.createTicketsReservation(event.getShortName(), creationRequest, principal); assertTrue(response.getStatusCode().is2xxSuccessful()); var body = response.getBody(); assertNotNull(body); @@ -152,26 +150,29 @@ void createSingleTicketWithMetadata() { var attributes = metadata.getMetadataForKey(TicketMetadataContainer.GENERAL); assertTrue(attributes.isPresent()); assertEquals(firstTicketProperties, attributes.get().getAttributes()); + var reservationMetadata = ticketReservationRepository.getMetadata(reservationId); + assertNotNull(reservationMetadata); + assertTrue(reservationMetadata.isHideContactData()); + assertFalse(reservationMetadata.isHideConfirmationButtons()); + assertTrue(reservationMetadata.isLockEmailEdit()); } @Test void createTwoTicketsWithMetadata() { var category = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId()).orElseThrow(); - var ticket = new TicketReservationModification(); - ticket.setQuantity(2); - ticket.setTicketCategoryId(category.getId()); - // metadata will be applied only to the first ticket var firstTicketProperties = Map.of("property", "value-first"); - ticket.setMetadata(List.of(firstTicketProperties)); - var creationRequest = new ReservationCreationRequest( + var ticket = new AttendeesByCategory(category.getId(), 2, null, List.of(firstTicketProperties)); + var creationRequest = new TicketReservationCreationRequest( List.of(ticket), List.of(), null, null, - "en" + null, + "en", + null ); var principal = new APITokenAuthentication(username, null, List.of()); - var response = controller.createReservation(event.getShortName(), creationRequest, principal); + var response = controller.createTicketsReservation(event.getShortName(), creationRequest, principal); assertTrue(response.getStatusCode().is2xxSuccessful()); var body = response.getBody(); assertNotNull(body); @@ -193,17 +194,17 @@ void createTwoTicketsWithMetadata() { var attributes = metadata.getMetadataForKey(TicketMetadataContainer.GENERAL); return attributes.isPresent() && attributes.get().getAttributes().equals(firstTicketProperties); }).count()); + var reservationMetadata = ticketReservationRepository.getMetadata(reservationId); + assertNotNull(reservationMetadata); + assertFalse(reservationMetadata.isHideContactData()); } @Test void createSingleTicketWithAuthenticatedUser() { configurationRepository.insert(OPENID_PUBLIC_ENABLED.name(), "true", ""); var category = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId()).orElseThrow(); - var ticket = new TicketReservationModification(); - ticket.setQuantity(1); - ticket.setTicketCategoryId(category.getId()); var firstTicketProperties = Map.of("property", "value-first"); - ticket.setMetadata(List.of(firstTicketProperties)); + var ticket = new AttendeesByCategory(category.getId(), 1, List.of(), List.of(firstTicketProperties)); var user = new ReservationUser( "test@example.org", "Test", @@ -211,15 +212,17 @@ void createSingleTicketWithAuthenticatedUser() { "test@example.org", "EXTERNALID" ); - var creationRequest = new ReservationCreationRequest( + var creationRequest = new TicketReservationCreationRequest( List.of(ticket), List.of(), + null, user, null, - "en" + "en", + null ); var principal = new APITokenAuthentication(username, null, List.of()); - var response = controller.createReservation(event.getShortName(), creationRequest, principal); + var response = controller.createTicketsReservation(event.getShortName(), creationRequest, principal); assertTrue(response.getStatusCode().is2xxSuccessful()); var body = response.getBody(); assertNotNull(body); @@ -246,4 +249,196 @@ void createSingleTicketWithAuthenticatedUser() { assertEquals(1, reservations.size()); assertEquals(reservationId, reservations.get(0).getId()); } + + @Test + void createSingleTicketWithAttendees() { + var category = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId()).orElseThrow(); + var firstTicketProperties = Map.of("property", "value-first"); + var ticket = new AttendeesByCategory(category.getId(), 1, List.of( + new AttendeeData("firstName", "lastName", "example@example.org", firstTicketProperties) + ), null); + var user = new ReservationUser( + "test@example.org", + "Test", + "McTest", + "test@example.org", + "EXTERNALID" + ); + var creationRequest = new TicketReservationCreationRequest( + List.of(ticket), + List.of(), + null, + user, + null, + "en", + null + ); + var principal = new APITokenAuthentication(username, null, List.of()); + var response = controller.createTicketsReservation(event.getShortName(), creationRequest, principal); + assertTrue(response.getStatusCode().is2xxSuccessful()); + var body = response.getBody(); + assertNotNull(body); + assertNull(body.getErrors()); + assertTrue(body.isSuccess()); + var reservationId = body.getId(); + assertNotNull(reservationId); + assertFalse(reservationId.isBlank()); + var href = body.getHref(); + assertFalse(StringUtils.startsWith(href, LOGGED_IN_RESERVATION_URL_PREFIX)); + + var tickets = ticketRepository.findTicketsInReservation(reservationId); + var savedTicket = tickets.get(0); + assertEquals(1, tickets.size()); + assertEquals("firstName", savedTicket.getFirstName()); + assertEquals("lastName", savedTicket.getLastName()); + assertEquals("example@example.org", savedTicket.getEmail()); + var metadata = ticketRepository.getTicketMetadata(savedTicket.getId()); + assertNotNull(metadata); + var attributes = metadata.getMetadataForKey(TicketMetadataContainer.GENERAL); + assertTrue(attributes.isPresent()); + assertEquals(firstTicketProperties, attributes.get().getAttributes()); + + var createdUser = userManager.findOptionalEnabledUserByUsername("test@example.org"); + assertFalse(createdUser.isPresent()); + } + + @Test + void createMultipleTicketsWithAttendees() { + var category = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId()).orElseThrow(); + var firstTicketProperties = Map.of("property", "value-first"); + var ticket = new AttendeesByCategory(category.getId(), 2, List.of( + new AttendeeData("firstName", "lastName", "example@example.org", firstTicketProperties), + new AttendeeData("firstName", "lastName", "example@example.org", firstTicketProperties) + ), null); + var user = new ReservationUser( + "test@example.org", + "Test", + "McTest", + "test@example.org", + "EXTERNALID" + ); + var creationRequest = new TicketReservationCreationRequest( + List.of(ticket), + List.of(), + null, + user, + null, + "en", + null + ); + var principal = new APITokenAuthentication(username, null, List.of()); + var response = controller.createTicketsReservation(event.getShortName(), creationRequest, principal); + assertTrue(response.getStatusCode().is2xxSuccessful()); + var body = response.getBody(); + assertNotNull(body); + assertNull(body.getErrors()); + assertTrue(body.isSuccess()); + var reservationId = body.getId(); + assertNotNull(reservationId); + assertFalse(reservationId.isBlank()); + var href = body.getHref(); + assertFalse(StringUtils.startsWith(href, LOGGED_IN_RESERVATION_URL_PREFIX)); + + var tickets = ticketRepository.findTicketsInReservation(reservationId); + assertEquals(2, tickets.size()); + tickets.forEach(savedTicket -> { + assertEquals("firstName", savedTicket.getFirstName()); + assertEquals("lastName", savedTicket.getLastName()); + assertEquals("example@example.org", savedTicket.getEmail()); + var metadata = ticketRepository.getTicketMetadata(savedTicket.getId()); + assertNotNull(metadata); + var attributes = metadata.getMetadataForKey(TicketMetadataContainer.GENERAL); + assertTrue(attributes.isPresent()); + assertEquals(firstTicketProperties, attributes.get().getAttributes()); + }); + var createdUser = userManager.findOptionalEnabledUserByUsername("test@example.org"); + assertFalse(createdUser.isPresent()); + } + + @Test + void createSingleTicketWithSubscriptionId() { + var subscriptionId = UUID.randomUUID().toString(); + var category = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId()).orElseThrow(); + var firstTicketProperties = Map.of("property", "value-first"); + var ticket = new AttendeesByCategory(category.getId(), 1, List.of( + new AttendeeData("firstName", "lastName", "example@example.org", firstTicketProperties) + ), null); + var user = new ReservationUser( + "test@example.org", + "Test", + "McTest", + "test@example.org", + "EXTERNALID" + ); + var creationRequest = new TicketReservationCreationRequest( + List.of(ticket), + List.of(), + null, + user, + null, + "en", + subscriptionId + ); + var principal = new APITokenAuthentication(username, null, List.of()); + var response = controller.createTicketsReservation(event.getShortName(), creationRequest, principal); + assertTrue(response.getStatusCode().is2xxSuccessful()); + var body = response.getBody(); + assertNotNull(body); + assertNull(body.getErrors()); + assertTrue(body.isSuccess()); + var reservationId = body.getId(); + assertNotNull(reservationId); + assertFalse(reservationId.isBlank()); + var href = body.getHref(); + assertFalse(StringUtils.startsWith(href, LOGGED_IN_RESERVATION_URL_PREFIX)); + assertTrue(StringUtils.endsWith(href, "subscription="+subscriptionId)); + var tickets = ticketRepository.findTicketsInReservation(reservationId); + assertEquals(1, tickets.size()); + tickets.forEach(savedTicket -> { + assertEquals("firstName", savedTicket.getFirstName()); + assertEquals("lastName", savedTicket.getLastName()); + assertEquals("example@example.org", savedTicket.getEmail()); + var metadata = ticketRepository.getTicketMetadata(savedTicket.getId()); + assertNotNull(metadata); + var attributes = metadata.getMetadataForKey(TicketMetadataContainer.GENERAL); + assertTrue(attributes.isPresent()); + assertEquals(firstTicketProperties, attributes.get().getAttributes()); + }); + var createdUser = userManager.findOptionalEnabledUserByUsername("test@example.org"); + assertFalse(createdUser.isPresent()); + } + + @Test + void createSubscriptionWithMetadata() { + configurationRepository.insert(ConfigurationKeys.STRIPE_PUBLIC_KEY.getValue(), "pk", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_SECRET_KEY.getValue(), "sk", ""); + var principal = new APITokenAuthentication(username, null, List.of()); + var creationResponse = subscriptionApiV1Controller.create(modificationRequest(SubscriptionDescriptor.SubscriptionUsageType.ONCE_PER_EVENT, true, clockProvider), principal); + assertTrue(creationResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(creationResponse.getBody()); + var descriptorId = creationResponse.getBody(); + var reservationRequest = new SubscriptionReservationCreationRequest(Map.of("key", "value"), + new ReservationUser("test@test.org", "Test", "Test1", "test@test.org", null), + "en", + new ReservationConfiguration(true, false, false), + null); + var reservationResponse = controller.createSubscriptionReservation(descriptorId, reservationRequest, principal); + assertTrue(reservationResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(reservationResponse.getBody()); + var body = Objects.requireNonNull(reservationResponse.getBody()); + assertTrue(body.isSuccess()); + var reservationId = body.getId(); + var reservation = ticketReservationRepository.findReservationById(reservationId); + assertEquals("Test", reservation.getFirstName()); + assertEquals("Test1", reservation.getLastName()); + assertEquals("test@test.org", reservation.getEmail()); + var subscriptions = subscriptionRepository.findSubscriptionsByReservationId(reservationId); + assertEquals(1, subscriptions.size()); + var subscriptionId = subscriptions.get(0).getId(); + var subscriptionMetadata = subscriptionRepository.getSubscriptionMetadata(subscriptionId); + assertNotNull(subscriptionMetadata); + assertNotNull(subscriptionMetadata.getProperties()); + assertFalse(subscriptionMetadata.getProperties().isEmpty()); + assertEquals("value", subscriptionMetadata.getProperties().get("key")); + } } diff --git a/src/test/java/alfio/controller/api/v1/SubscriptionApiV1IntegrationTest.java b/src/test/java/alfio/controller/api/v1/SubscriptionApiV1IntegrationTest.java new file mode 100644 index 0000000000..ce8029c08b --- /dev/null +++ b/src/test/java/alfio/controller/api/v1/SubscriptionApiV1IntegrationTest.java @@ -0,0 +1,196 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v1; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import alfio.controller.api.ControllerConfiguration; +import alfio.controller.api.v1.admin.EventApiV1Controller; +import alfio.controller.api.v1.admin.SubscriptionApiV1Controller; +import alfio.manager.user.UserManager; +import alfio.model.PriceContainer; +import alfio.model.TicketCategory; +import alfio.model.api.v1.admin.EventCreationRequest; +import alfio.model.api.v1.admin.SubscriptionDescriptorModificationRequest; +import alfio.model.api.v1.admin.subscription.StandardPeriodTerm; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.modification.DateTimeModification; +import alfio.model.modification.OrganizationModification; +import alfio.model.modification.TicketCategoryModification; +import alfio.model.subscription.SubscriptionDescriptor; +import alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType; +import alfio.model.transaction.PaymentProxy; +import alfio.model.user.Role; +import alfio.model.user.User; +import alfio.repository.SubscriptionRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; +import alfio.test.util.IntegrationTestUtil; +import alfio.util.ClockProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.security.Principal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import static alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType.ONCE_PER_EVENT; +import static alfio.model.subscription.SubscriptionDescriptor.SubscriptionUsageType.UNLIMITED; +import static alfio.test.util.IntegrationTestUtil.AVAILABLE_SEATS; +import static alfio.test.util.IntegrationTestUtil.DESCRIPTION; +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.*; + +@AlfioIntegrationTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class SubscriptionApiV1IntegrationTest { + + @Autowired + UserManager userManager; + @Autowired + OrganizationRepository organizationRepository; + @Autowired + ConfigurationRepository configurationRepository; + @Autowired + SubscriptionApiV1Controller controller; + @Autowired + EventApiV1Controller eventController; + @Autowired + ClockProvider clockProvider; + @Autowired + SubscriptionRepository subscriptionRepository; + + SubscriptionDescriptor subscriptionDescriptor; + + String username; + Principal principal; + + @BeforeEach + public void ensureConfiguration() { + IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); + + var organizationName = UUID.randomUUID().toString(); + this.username = UUID.randomUUID().toString(); + + var organizationModification = new OrganizationModification(null, organizationName, "email@example.com", "org", null, null); + userManager.createOrganization(organizationModification, null); + var organization = organizationRepository.findByName(organizationName).orElseThrow(); + userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.API_CONSUMER, User.Type.INTERNAL, null); + + this.principal = Mockito.mock(Principal.class); + Mockito.when(principal.getName()).thenReturn(username); + var creationRequest = creationRequest(); + var result = controller.create(creationRequest, principal); + assertTrue(result.getStatusCode().is2xxSuccessful()); + assertNotNull(result.getBody()); + var descriptorOptional = subscriptionRepository.findOne(UUID.fromString(result.getBody())); + assertTrue(descriptorOptional.isPresent()); + this.subscriptionDescriptor = descriptorOptional.get(); + } + + @Test + void update() { + assertEquals(ONCE_PER_EVENT, subscriptionDescriptor.getUsageType()); + assertTrue(subscriptionDescriptor.isPublic()); + + var updateRequest = modificationRequest(UNLIMITED, false); + var result = controller.update(subscriptionDescriptor.getId(), updateRequest, principal); + assertTrue(result.getStatusCode().is2xxSuccessful()); + assertNotNull(result.getBody()); + var uuid = UUID.fromString(result.getBody()); + assertEquals(subscriptionDescriptor.getId(), uuid); + var updated = subscriptionRepository.findOne(uuid).orElseThrow(); + assertEquals(UNLIMITED, updated.getUsageType()); + assertFalse(updated.isPublic()); + } + + @Test + void deactivate() { + var result = controller.deactivate(subscriptionDescriptor.getId(), principal); + assertTrue(result.getStatusCode().is2xxSuccessful()); + assertTrue(subscriptionRepository.findOne(subscriptionDescriptor.getId()).isEmpty()); + } + + @Test + void updateLinkedEvents() { + var eventCreateResponse = eventController.create(EventApiV1IntegrationTest.creationRequest("short-name"), principal); + assertNotNull(eventCreateResponse.getBody()); + var eventSlug = eventCreateResponse.getBody(); + var descriptorId = subscriptionDescriptor.getId(); + var response = controller.getLinkedEvents(descriptorId, principal); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertTrue(requireNonNull(response.getBody()).isEmpty()); + controller.updateLinkedEvents(descriptorId, List.of(eventSlug), principal); + response = controller.getLinkedEvents(descriptorId, principal); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(List.of(eventSlug), response.getBody()); + } + + + + private SubscriptionDescriptorModificationRequest modificationRequest(SubscriptionUsageType usageType, + boolean isPublic) { + return modificationRequest(usageType, isPublic, clockProvider); + } + + static SubscriptionDescriptorModificationRequest modificationRequest(SubscriptionUsageType usageType, + boolean isPublic, + ClockProvider clockProvider) { + return new SubscriptionDescriptorModificationRequest( + usageType, + SubscriptionDescriptorModificationRequest.TERM_STANDARD, + new StandardPeriodTerm(SubscriptionDescriptor.SubscriptionTimeUnit.MONTHS, 1), + List.of(new EventCreationRequest.DescriptionRequest("en", "this is the title")), + List.of(new EventCreationRequest.DescriptionRequest("en", "this is the description")), + null, + LocalDateTime.now(clockProvider.getClock()), + LocalDateTime.now(clockProvider.getClock()).plusMonths(5), + new BigDecimal("10.00"), + new BigDecimal("7.7"), + PriceContainer.VatStatus.INCLUDED, + "CHF", + isPublic, + "https://alf.io/img/tutorials/check-in-app/003.png", + "https://alf.io", + "https://alf.io", + "Europe/Zurich", + false, + List.of(PaymentProxy.STRIPE) + ); + } + + private SubscriptionDescriptorModificationRequest creationRequest() { + return modificationRequest(ONCE_PER_EVENT, true); + } + +} diff --git a/src/test/java/alfio/controller/api/v2/user/PollApiControllerIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/PollApiControllerIntegrationTest.java index 55030dfa19..bcf4e415da 100644 --- a/src/test/java/alfio/controller/api/v2/user/PollApiControllerIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/PollApiControllerIntegrationTest.java @@ -36,6 +36,7 @@ import alfio.repository.*; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -48,11 +49,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; @@ -62,10 +61,9 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class PollApiControllerIntegrationTest { private static final Logger LOGGER = LoggerFactory.getLogger(PollApiControllerIntegrationTest.class); @@ -119,7 +117,7 @@ void init() { pollId = rowCountAndKey.getKey(); LOGGER.info("pollId {}", pollId); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(1); + tr.setQuantity(1); TicketCategory category = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); tr.setTicketCategoryId(category.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -196,9 +194,9 @@ void getSingle() { assertTrue(response.getBody().isSuccess()); assertNotNull(response.getBody().getValue()); var pollWithOptions = response.getBody().getValue(); - assertEquals(2, pollWithOptions.getOptions().size()); - assertEquals("first", pollWithOptions.getOptions().get(0).getTitle().get("en")); - assertEquals("second", pollWithOptions.getOptions().get(1).getTitle().get("en")); + assertEquals(2, pollWithOptions.options().size()); + assertEquals("first", pollWithOptions.options().get(0).title().get("en")); + assertEquals("second", pollWithOptions.options().get(1).title().get("en")); } @Test @@ -224,8 +222,8 @@ void registerAnswer() { var statistics = pollRepository.getStatisticsFor(pollId, event.getId()); assertEquals(1, statistics.size()); - assertEquals(firstOptionId, statistics.get(0).getOptionId()); - assertEquals(1, statistics.get(0).getVotes()); + assertEquals(firstOptionId, statistics.get(0).optionId()); + assertEquals(1, statistics.get(0).votes()); // update vote form.setOptionId(secondOptionId); @@ -233,8 +231,8 @@ void registerAnswer() { assertTrue(response.getStatusCode().is2xxSuccessful()); statistics = pollRepository.getStatisticsFor(pollId, event.getId()); assertEquals(1, statistics.size()); - assertEquals(secondOptionId, statistics.get(0).getOptionId()); - assertEquals(1, statistics.get(0).getVotes()); + assertEquals(secondOptionId, statistics.get(0).optionId()); + assertEquals(1, statistics.get(0).votes()); } private void updateVisibility(Poll.PollStatus status) { diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java index 8cfdb7cf3c..f79421a870 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java @@ -16,6 +16,7 @@ */ package alfio.controller.api.v2.user.reservation; +import alfio.config.authentication.support.APITokenAuthentication; import alfio.controller.IndexController; import alfio.controller.api.admin.AdditionalServiceApiController; import alfio.controller.api.admin.CheckInApiController; @@ -27,6 +28,7 @@ import alfio.controller.api.v2.model.BasicEventInfo; import alfio.controller.api.v2.model.EventCode; import alfio.controller.api.v2.model.Language; +import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.user.EventApiV2Controller; import alfio.controller.api.v2.user.ReservationApiV2Controller; import alfio.controller.api.v2.user.TicketApiV2Controller; @@ -36,6 +38,7 @@ import alfio.extension.ExtensionService; import alfio.manager.*; import alfio.manager.support.CheckInStatus; +import alfio.manager.support.IncompatibleStateException; import alfio.manager.support.TicketAndCheckInResult; import alfio.manager.support.extension.ExtensionEvent; import alfio.model.*; @@ -62,42 +65,51 @@ import com.google.zxing.common.HybridBinarizer; import com.google.zxing.qrcode.QRCodeReader; import com.opencsv.CSVReader; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Assertions; import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.web.context.request.ServletWebRequest; import javax.imageio.ImageIO; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStreamReader; import java.io.StringReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.security.Principal; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; +import static alfio.config.authentication.support.AuthenticationConstants.SYSTEM_API_CLIENT; import static alfio.manager.support.extension.ExtensionEvent.*; +import static alfio.model.system.ConfigurationKeys.TRANSLATION_OVERRIDE; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; -@RequiredArgsConstructor -@Log4j2 + public abstract class BaseReservationFlowTest extends BaseIntegrationTest { + private static final Logger log = LoggerFactory.getLogger(BaseReservationFlowTest.class); + protected final ConfigurationRepository configurationRepository; protected final EventManager eventManager; protected final EventRepository eventRepository; @@ -130,23 +142,101 @@ public abstract class BaseReservationFlowTest extends BaseIntegrationTest { protected final ClockProvider clockProvider; protected final NotificationManager notificationManager; protected final UserRepository userRepository; + protected final OrganizationDeleter organizationDeleter; + protected final PromoCodeDiscountRepository promoCodeDiscountRepository; + protected final PromoCodeRequestManager promoCodeRequestManager; + protected final ExportManager exportManager; private Integer additionalServiceId; - private static final String PROMO_CODE = "MYPROMOCODE"; + static final String PROMO_CODE = "MYPROMOCODE"; private static final String HIDDEN_CODE = "HIDDENNN"; static final String URL_CODE_HIDDEN = "CODE_CODE_CODE"; private int hiddenCategoryId = Integer.MIN_VALUE; + protected BaseReservationFlowTest(ConfigurationRepository configurationRepository, + EventManager eventManager, + EventRepository eventRepository, + EventStatisticsManager eventStatisticsManager, + TicketCategoryRepository ticketCategoryRepository, + TicketReservationRepository ticketReservationRepository, + EventApiController eventApiController, + TicketRepository ticketRepository, + TicketFieldRepository ticketFieldRepository, + AdditionalServiceApiController additionalServiceApiController, + SpecialPriceTokenGenerator specialPriceTokenGenerator, + SpecialPriceRepository specialPriceRepository, + CheckInApiController checkInApiController, + AttendeeApiController attendeeApiController, + UsersApiController usersApiController, + ScanAuditRepository scanAuditRepository, + AuditingRepository auditingRepository, + AdminReservationManager adminReservationManager, + TicketReservationManager ticketReservationManager, + InfoApiController infoApiController, + TranslationsApiController translationsApiController, + EventApiV2Controller eventApiV2Controller, + ReservationApiV2Controller reservationApiV2Controller, + TicketApiV2Controller ticketApiV2Controller, + IndexController indexController, + NamedParameterJdbcTemplate jdbcTemplate, + ExtensionLogRepository extensionLogRepository, + ExtensionService extensionService, + PollRepository pollRepository, + ClockProvider clockProvider, + NotificationManager notificationManager, + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + ExportManager exportManager) { + this.configurationRepository = configurationRepository; + this.eventManager = eventManager; + this.eventRepository = eventRepository; + this.eventStatisticsManager = eventStatisticsManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.eventApiController = eventApiController; + this.ticketRepository = ticketRepository; + this.ticketFieldRepository = ticketFieldRepository; + this.additionalServiceApiController = additionalServiceApiController; + this.specialPriceTokenGenerator = specialPriceTokenGenerator; + this.specialPriceRepository = specialPriceRepository; + this.checkInApiController = checkInApiController; + this.attendeeApiController = attendeeApiController; + this.usersApiController = usersApiController; + this.scanAuditRepository = scanAuditRepository; + this.auditingRepository = auditingRepository; + this.adminReservationManager = adminReservationManager; + this.ticketReservationManager = ticketReservationManager; + this.infoApiController = infoApiController; + this.translationsApiController = translationsApiController; + this.eventApiV2Controller = eventApiV2Controller; + this.reservationApiV2Controller = reservationApiV2Controller; + this.ticketApiV2Controller = ticketApiV2Controller; + this.indexController = indexController; + this.jdbcTemplate = jdbcTemplate; + this.extensionLogRepository = extensionLogRepository; + this.extensionService = extensionService; + this.pollRepository = pollRepository; + this.clockProvider = clockProvider; + this.notificationManager = notificationManager; + this.userRepository = userRepository; + this.organizationDeleter = organizationDeleter; + this.promoCodeDiscountRepository = promoCodeDiscountRepository; + this.promoCodeRequestManager = promoCodeRequestManager; + this.exportManager = exportManager; + } + private void ensureConfiguration(ReservationFlowContext context) { IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); //promo code at event level - eventManager.addPromoCode(PROMO_CODE, context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DISCOUNT, null); + eventManager.addPromoCode(PROMO_CODE, context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DISCOUNT, null, null); - hiddenCategoryId = ticketCategoryRepository.findAllTicketCategories(context.event.getId()).stream().filter(TicketCategory::isAccessRestricted).collect(Collectors.toList()).get(0).getId(); + hiddenCategoryId = ticketCategoryRepository.findAllTicketCategories(context.event.getId()).stream().filter(TicketCategory::isAccessRestricted).toList().get(0).getId(); - eventManager.addPromoCode(HIDDEN_CODE, context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 0, PromoCodeDiscount.DiscountType.NONE, null, null, "hidden", "test@test.ch", PromoCodeDiscount.CodeType.ACCESS, hiddenCategoryId); + eventManager.addPromoCode(HIDDEN_CODE, context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 0, PromoCodeDiscount.DiscountType.NONE, null, null, "hidden", "test@test.ch", PromoCodeDiscount.CodeType.ACCESS, hiddenCategoryId, null); // add additional fields before and after, with one mandatory @@ -195,14 +285,13 @@ private void ensureConfiguration(ReservationFlowContext context) { specialPriceTokenGenerator.generatePendingCodes(); } + protected Stream<String> getExtensionEventsToRegister() { + return allEvents(); + } + protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) throws Exception { // as soon as the test starts, insert the extension in the database (prepare the environment) - try (var extensionInputStream = requireNonNull(getClass().getResourceAsStream("/extension.js"))) { - List<String> extensionStream = IOUtils.readLines(new InputStreamReader(extensionInputStream, StandardCharsets.UTF_8)); - String concatenation = String.join("\n", extensionStream).replace("EVENTS", Arrays.stream(ExtensionEvent.values()).map(ee -> "'"+ee.name()+"'").collect(Collectors.joining(","))); - extensionService.createOrUpdate(null, null, new Extension("-", "syncName", concatenation.replace("placeHolder", "false"), true)); - extensionService.createOrUpdate(null, null, new Extension("-", "asyncName", concatenation.replace("placeHolder", "true"), true)); - } + insertExtension(extensionService, "/extension.js", getExtensionEventsToRegister()); List<BasicEventInfo> body = eventApiV2Controller.listEvents(SearchOptions.empty()).getBody(); assertNotNull(body); assertTrue(body.isEmpty()); @@ -211,6 +300,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t var context = contextSupplier.get(); ensureConfiguration(context); + Assertions.assertEquals(1, eventManager.getEventsCount()); // check if EVENT_CREATED was logged List<ExtensionLog> extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); @@ -304,7 +394,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertEquals(context.event.getFileBlobId(), selectedEvent.getFileBlobId()); assertTrue(selectedEvent.getI18nOverride().isEmpty()); - configurationRepository.insert("TRANSLATION_OVERRIDE", Json.toJson(Map.of("en", Map.of("show-context.event.tickets.left", "{0} left!"))), ""); + configurationRepository.insert(TRANSLATION_OVERRIDE.name(), Json.toJson(Map.of("en", Map.of("show-context.event.tickets.left", "{0} left!"))), ""); configurationRepository.insertEventLevel(context.event.getOrganizationId(), context.event.getId(),"TRANSLATION_OVERRIDE", Json.toJson(Map.of("en", Map.of("common.vat", "context.event.vat"))), ""); eventRes = eventApiV2Controller.getEvent(context.event.getShortName(), new MockHttpSession()); selectedEvent = eventRes.getBody(); @@ -423,7 +513,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t // dynamic promo codes can be applied only automatically { - eventManager.addPromoCode("DYNAMIC_CODE", context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DYNAMIC, null); + eventManager.addPromoCode("DYNAMIC_CODE", context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DYNAMIC, null, null); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, eventApiV2Controller.validateCode(context.event.getShortName(), "DYNAMIC_CODE").getStatusCode()); // try to enter it anyway @@ -431,7 +521,23 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t var ticketReservation = new TicketReservationModification(); form.setPromoCode("DYNAMIC_CODE"); ticketReservation.setQuantity(1); - ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); + ticketReservation.setTicketCategoryId(retrieveCategories(context).get(0).getId()); + form.setReservation(Collections.singletonList(ticketReservation)); + var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), null); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode()); + } + + // promo code currency code must match with event's in order to be redeemed + { + eventManager.addPromoCode("TEST_TEST_TEST_TEST", null, context.event.getOrganizationId(), ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.FIXED_AMOUNT, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DISCOUNT, null, "JPY"); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, eventApiV2Controller.validateCode(context.event.getShortName(), "TEST_TEST_TEST_TEST").getStatusCode()); + + // try to enter it anyway + var form = new ReservationForm(); + var ticketReservation = new TicketReservationModification(); + form.setPromoCode("TEST_TEST_TEST_TEST"); + ticketReservation.setQuantity(1); + ticketReservation.setTicketCategoryId(retrieveCategories(context).get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), null); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode()); @@ -474,7 +580,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertEquals(HttpStatus.OK, reservationInfo.getStatusCode()); assertNotNull(reservationInfo.getBody()); assertEquals("1.00", reservationInfo.getBody().getOrderSummary().getTotalPrice()); - assertEquals("hidden", reservationInfo.getBody().getOrderSummary().getSummary().get(0).getName()); + assertEquals("hidden", reservationInfo.getBody().getOrderSummary().getSummary().get(0).name()); var activePaymentMethods = reservationInfo.getBody().getActivePaymentMethods(); assertFalse(activePaymentMethods.isEmpty()); @@ -601,7 +707,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); ticketReservation.setQuantity(1); - ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); + ticketReservation.setTicketCategoryId(retrieveCategories(context).get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser()); assertEquals(HttpStatus.OK, res.getStatusCode()); @@ -657,7 +763,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); ticketReservation.setQuantity(2); - ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); + ticketReservation.setTicketCategoryId(retrieveCategories(context).get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var additionalService = new AdditionalServiceReservationModification(); @@ -678,13 +784,13 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertNotNull(resInfoRes.getBody()); var ticketsByCat = resInfoRes.getBody().getTicketsByCategory(); assertEquals(1, ticketsByCat.size()); - assertEquals(2, ticketsByCat.get(0).getTickets().size()); + assertEquals(2, ticketsByCat.get(0).tickets().size()); - var ticket1 = ticketsByCat.get(0).getTickets().get(0); + var ticket1 = ticketsByCat.get(0).tickets().get(0); assertEquals(1, ticket1.getTicketFieldConfigurationBeforeStandard().size()); // 1 assertEquals(2, ticket1.getTicketFieldConfigurationAfterStandard().size()); // 1 + 1 additional service related field (appear only on first ticket) - var ticket2 = ticketsByCat.get(0).getTickets().get(1); + var ticket2 = ticketsByCat.get(0).tickets().get(1); assertEquals(1, ticket2.getTicketFieldConfigurationBeforeStandard().size()); // 1 assertEquals(1, ticket2.getTicketFieldConfigurationAfterStandard().size()); // 1 @@ -738,13 +844,16 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t } - //buy one ticket, without discount + //buy one ticket { var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); ticketReservation.setQuantity(1); - ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); + ticketReservation.setTicketCategoryId(retrieveCategories(context).get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); + if (context.applyDiscount) { + form.setPromoCode(PROMO_CODE); + } var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser()); assertEquals(HttpStatus.OK, res.getStatusCode()); var resBody = res.getBody(); @@ -762,13 +871,13 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertNotNull(reservation); assertEquals(reservationId, reservation.getId()); assertEquals(1, reservation.getTicketsByCategory().size()); - assertEquals(1, reservation.getTicketsByCategory().get(0).getTickets().size()); + assertEquals(1, reservation.getTicketsByCategory().get(0).tickets().size()); - var selectedTicket = reservation.getTicketsByCategory().get(0).getTickets().get(0); - assertEquals("field1", selectedTicket.getTicketFieldConfigurationBeforeStandard().get(0).getName()); - assertTrue(selectedTicket.getTicketFieldConfigurationBeforeStandard().get(0).isRequired()); - assertEquals("field2", selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).getName()); - assertFalse(selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).isRequired()); + var selectedTicket = reservation.getTicketsByCategory().get(0).tickets().get(0); + assertEquals("field1", selectedTicket.getTicketFieldConfigurationBeforeStandard().get(0).name()); + assertTrue(selectedTicket.getTicketFieldConfigurationBeforeStandard().get(0).required()); + assertEquals("field2", selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).name()); + assertFalse(selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).required()); var contactForm = new ContactAndTicketsForm(); var validationErrorsRes = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication()); @@ -785,11 +894,13 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t contactForm.setFirstName("full"); contactForm.setLastName("name"); + customizeContactFormForSuccessfulReservation(contactForm); + var ticketForm = new UpdateTicketOwnerForm(); ticketForm.setFirstName("ticketfull"); ticketForm.setLastName("ticketname"); ticketForm.setEmail("tickettest@test.com"); - contactForm.setTickets(Collections.singletonMap(reservation.getTicketsByCategory().get(0).getTickets().get(0).getUuid(), ticketForm)); + contactForm.setTickets(Collections.singletonMap(reservation.getTicketsByCategory().get(0).tickets().get(0).getUuid(), ticketForm)); var overviewResFailed = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication()); assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, overviewResFailed.getStatusCode()); @@ -826,73 +937,19 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertTrue(owner.isEmpty()); } - var paymentForm = new PaymentForm(); - var handleResError = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), - new MockHttpServletRequest(), context.getPublicUser()); - assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, handleResError.getStatusCode()); - - - paymentForm.setPrivacyPolicyAccepted(true); - paymentForm.setTermAndConditionsAccepted(true); - paymentForm.setPaymentProxy(PaymentProxy.OFFLINE); - paymentForm.setSelectedPaymentMethod(PaymentMethod.BANK_TRANSFER); - - // bank transfer does not have a transaction, it's created on confirmOverview call - var tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER"); - assertEquals(HttpStatus.NOT_FOUND, tStatus.getStatusCode()); - // - - var handleRes = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), - new MockHttpServletRequest(), context.getPublicUser()); - - assertEquals(HttpStatus.OK, handleRes.getStatusCode()); + int promoCodeId = promoCodeDiscountRepository.findPromoCodeInEventOrOrganization(context.event.getId(), PROMO_CODE).orElseThrow().getId(); - checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT, context); + // initialize and confirm payment + performAndValidatePayment(context, reservationId, promoCodeId, this::cleanupExtensionLog); - tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER"); - assertEquals(HttpStatus.OK, tStatus.getStatusCode()); - assertNotNull(tStatus.getBody()); - assertFalse(tStatus.getBody().isSuccess()); + ensureReservationIsComplete(reservationId, context); reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody(); assertNotNull(reservation); var orderSummary = reservation.getOrderSummary(); - assertTrue(orderSummary.isNotYetPaid()); - assertEquals("10.00", orderSummary.getTotalPrice()); - assertEquals("0.10", orderSummary.getTotalVAT()); - assertEquals("1.00", orderSummary.getVatPercentage()); - - //clear the extension_log table so that we can check the expectation - cleanupExtensionLog(); - - validatePayment(context.event.getShortName(), reservationId, context); - - extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); - - boolean online = containsOnlineTickets(context, reservationId); - assertEventLogged(extLogs, RESERVATION_CONFIRMED, online ? 12 : 10); - assertEventLogged(extLogs, CONFIRMATION_MAIL_CUSTOM_TEXT, online ? 12 : 10); - assertEventLogged(extLogs, TICKET_ASSIGNED, online ? 12 : 10); - if(online) { - assertEventLogged(extLogs, CUSTOM_ONLINE_JOIN_URL, 12); - } - assertEventLogged(extLogs, TICKET_ASSIGNED_GENERATE_METADATA, online ? 12 : 10); - assertEventLogged(extLogs, TICKET_MAIL_CUSTOM_TEXT, online ? 12 : 10); - - - - checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.COMPLETE, context); - - tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER"); - assertEquals(HttpStatus.OK, tStatus.getStatusCode()); - assertNotNull(tStatus.getBody()); - assertTrue(tStatus.getBody().isSuccess()); - - reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody(); - assertNotNull(reservation); - orderSummary = reservation.getOrderSummary(); assertFalse(orderSummary.isNotYetPaid()); + checkDiscountUsage(reservationId, promoCodeId, context); var confRes = reservationApiV2Controller.reSendReservationConfirmationEmail(PurchaseContextType.event, context.event.getShortName(), reservationId, "en", context.getPublicUser()); assertEquals(HttpStatus.OK, confRes.getStatusCode()); @@ -902,7 +959,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t // trigger email processing triggerEmailProcessingAndCheck(context, reservationId); - var ticket = reservation.getTicketsByCategory().stream().findFirst().orElseThrow().getTickets().get(0); + var ticket = reservation.getTicketsByCategory().stream().findFirst().orElseThrow().tickets().get(0); assertEquals("tickettest@test.com", ticket.getEmail()); assertEquals("ticketfull", ticket.getFirstName()); assertEquals("ticketname", ticket.getLastName()); @@ -949,7 +1006,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertEquals("full name", ticketFoundBody.getReservationFullName()); reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody(); assertNotNull(reservation); - ticket = reservation.getTicketsByCategory().stream().findFirst().orElseThrow().getTickets().get(0); + ticket = reservation.getTicketsByCategory().stream().findFirst().orElseThrow().tickets().get(0); assertEquals("testmctest@test.com", ticket.getEmail()); assertEquals("Test", ticket.getFirstName()); assertEquals("Testson", ticket.getLastName()); @@ -957,7 +1014,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t var ticketPdfMockResp = new MockHttpServletResponse(); ticketApiV2Controller.generateTicketPdf(context.event.getShortName(), ticket.getUuid(), ticketPdfMockResp); - assertEquals("application/pdf", ticketPdfMockResp.getContentType()); + assertEquals(MediaType.APPLICATION_PDF_VALUE, ticketPdfMockResp.getContentType()); var ticketQRCodeResp = new MockHttpServletResponse(); ticketApiV2Controller.showQrCode(context.event.getShortName(), ticket.getUuid(), ticketQRCodeResp); @@ -966,7 +1023,7 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t var fullTicketInfo = ticketRepository.findByUUID(ticket.getUuid()); var qrCodeReader = new QRCodeReader(); var qrCodeRead = qrCodeReader.decode(new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(ImageIO.read(new ByteArrayInputStream(ticketQRCodeResp.getContentAsByteArray()))))), Map.of(DecodeHintType.PURE_BARCODE, Boolean.TRUE)); - assertEquals(fullTicketInfo.ticketCode(context.event.getPrivateKey()), qrCodeRead.getText()); + assertEquals(fullTicketInfo.ticketCode(context.event.getPrivateKey(), context.event.supportsQRCodeCaseInsensitive()), qrCodeRead.getText()); //can only be done for free tickets var releaseTicketFailure = ticketApiV2Controller.releaseTicket(context.event.getShortName(), ticket.getUuid()); @@ -975,8 +1032,8 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t //no invoice, but receipt - assertEquals(HttpStatus.NOT_FOUND, reservationApiV2Controller.getInvoice(context.event.getShortName(), reservationId, new MockHttpServletResponse(), context.getPublicAuthentication()).getStatusCode()); - assertEquals(HttpStatus.OK, reservationApiV2Controller.getReceipt(context.event.getShortName(), reservationId, new MockHttpServletResponse(), context.getPublicAuthentication()).getStatusCode()); + assertEquals(contactForm.isInvoiceRequested() ? HttpStatus.OK : HttpStatus.NOT_FOUND, reservationApiV2Controller.getInvoice(context.event.getShortName(), reservationId, new MockHttpServletResponse(), context.getPublicAuthentication()).getStatusCode()); + assertEquals(contactForm.isInvoiceRequested() ? HttpStatus.NOT_FOUND : HttpStatus.OK, reservationApiV2Controller.getReceipt(context.event.getShortName(), reservationId, new MockHttpServletResponse(), context.getPublicAuthentication()).getStatusCode()); @@ -991,20 +1048,44 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t String ticketIdentifier = fullTicketInfo.getUuid(); String eventName = context.event.getShortName(); - String ticketCode = fullTicketInfo.ticketCode(context.event.getPrivateKey()); + // try to search ticket + var results = checkInApiController.searchAttendees(eventName, fullTicketInfo.getEmail(), 0, principal); + switch (context.event.getFormat()) { + case IN_PERSON: + case HYBRID: + assertTrue(results.getStatusCode().is2xxSuccessful()); + assertNotNull(results.getBody()); + var searchResults = results.getBody(); + assertEquals(1, searchResults.getTotalPages()); + var attendees = searchResults.getAttendees(); + int count = searchResults.getTotalResults(); + assertFalse(searchResults.hasMorePages()); + assertFalse(attendees.isEmpty()); + assertEquals(count, attendees.size()); + assertTrue(attendees.stream().anyMatch(sr -> sr.getLastName().equals(fullTicketInfo.getLastName()))); + assertTrue(attendees.stream().allMatch(sr -> sr.getAdditionalInfo() != null)); + break; + case ONLINE: + assertTrue(results.getStatusCode().is2xxSuccessful()); + assertNotNull(results.getBody()); + assertEquals(0, results.getBody().getTotalResults()); + break; + } + + String ticketCode = fullTicketInfo.ticketCode(context.event.getPrivateKey(), context.event.supportsQRCodeCaseInsensitive()); TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode); assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus()); CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode(); tc.setCode(ticketCode); - assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken("ciccio", "ciccio")).getResult().getStatus()); + assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken(context.userId + "_api", "")).getResult().getStatus()); List<ScanAudit> audits = scanAuditRepository.findAllForEvent(context.event.getId()); assertFalse(audits.isEmpty()); - assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier))); + assertTrue(audits.stream().anyMatch(sa -> sa.ticketUuid().equals(ticketIdentifier))); extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); assertEventLogged(extLogs, TICKET_CHECKED_IN, 2); - + validateCheckInData(context); TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode); assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus()); @@ -1017,6 +1098,30 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertEquals(20, eventWithAdditionalInfo3.getAvailableSeats()); assertEquals(1, eventWithAdditionalInfo3.getCheckedInTickets()); + // at this point we should not be able to cancel the reservation anymore + var removeResult = adminReservationManager.removeReservation( + PurchaseContextType.event, + context.event.getShortName(), + reservationId, + true, + true, + true, + context.userId + ); + assertFalse(removeResult.isSuccess()); + + // trying to remove the ticket would result in a runtime exception + assertThrows(IncompatibleStateException.class, () -> adminReservationManager.removeTickets( + context.event.getShortName(), + reservationId, + List.of(fullTicketInfo.getId()), + List.of(), + false, + false, + context.userId + )); + + checkReservationExport(context); //test revert check in assertTrue(checkInApiController.revertCheckIn(context.event.getId(), ticketIdentifier, principal)); @@ -1030,9 +1135,9 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t Mockito.when(sponsorPrincipal.getName()).thenReturn(sponsorUser.getUsername()); // check failures - assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket", null, null), sponsorPrincipal).getBody().getResult().getStatus()); - assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket", null, null), sponsorPrincipal).getBody().getResult().getStatus()); - assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier, null, null), sponsorPrincipal).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket", null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket", null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier, null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); // @@ -1089,7 +1194,9 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t // check register sponsor scan success flow assertTrue(attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().isEmpty()); - assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketwc.getUuid(), null, null), sponsorPrincipal).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketwc.getUuid(), null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketwc.getUuid(), null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); + // scanned badges returns only unique values for a limited subset of columns assertEquals(1, attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size()); // check export @@ -1104,14 +1211,18 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]); assertEquals("", csvSponsorScan.get(1)[8]); assertEquals(SponsorScan.LeadStatus.WARM.name(), csvSponsorScan.get(1)[9]); + assertEquals(AttendeeManager.DEFAULT_OPERATOR_ID, csvSponsorScan.get(1)[10]); // // check update notes - assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid(), "this is a very good lead!", "HOT"), sponsorPrincipal).getBody().getResult().getStatus()); - assertEquals(1, attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size()); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid(), "this is a very good lead!", "HOT"), sponsorPrincipal, null).getBody().getResult().getStatus()); + var scannedBadges = attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody(); + assertEquals(1, requireNonNull(scannedBadges).size()); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid(), "this is a very good lead!", "HOT"), sponsorPrincipal, null).getBody().getResult().getStatus()); + scannedBadges = attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody(); + assertEquals(1, requireNonNull(scannedBadges).size()); response = new MockHttpServletResponse(); eventApiController.downloadSponsorScanExport(context.event.getShortName(), "csv", response, principal); - response.getContentAsString(); csvReader = new CSVReader(new StringReader(response.getContentAsString())); csvSponsorScan = csvReader.readAll(); assertEquals(2, csvSponsorScan.size()); @@ -1120,6 +1231,27 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]); assertEquals("this is a very good lead!", csvSponsorScan.get(1)[8]); assertEquals(SponsorScan.LeadStatus.HOT.name(), csvSponsorScan.get(1)[9]); + assertEquals(AttendeeManager.DEFAULT_OPERATOR_ID, csvSponsorScan.get(1)[10]); + + // scan from a different operator + response = new MockHttpServletResponse(); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketwc.getUuid(), null, null), sponsorPrincipal, "OP2").getBody().getResult().getStatus()); + eventApiController.downloadSponsorScanExport(context.event.getShortName(), "csv", response, principal); + csvReader = new CSVReader(new StringReader(response.getContentAsString())); + csvSponsorScan = csvReader.readAll(); + assertEquals(3, csvSponsorScan.size()); + assertEquals("sponsor", csvSponsorScan.get(1)[0]); + assertEquals("Test Testson", csvSponsorScan.get(1)[3]); + assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]); + assertEquals("this is a very good lead!", csvSponsorScan.get(1)[8]); + assertEquals(SponsorScan.LeadStatus.HOT.name(), csvSponsorScan.get(1)[9]); + assertEquals(AttendeeManager.DEFAULT_OPERATOR_ID, csvSponsorScan.get(1)[10]); + + assertEquals("sponsor", csvSponsorScan.get(2)[0]); + assertEquals("Test Testson", csvSponsorScan.get(2)[3]); + assertEquals("testmctest@test.com", csvSponsorScan.get(2)[4]); + assertEquals("", csvSponsorScan.get(2)[8]); + assertEquals("OP2", csvSponsorScan.get(2)[10]); // #742 - test multiple check-ins @@ -1166,8 +1298,138 @@ protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) t } performAdditionalTests(context); eventManager.deleteEvent(context.event.getId(), context.userId); + assertTrue(organizationDeleter.deleteOrganization(context.event.getOrganizationId(), new APITokenAuthentication("TEST", "", List.of(new SimpleGrantedAuthority("ROLE_" + SYSTEM_API_CLIENT))))); + } + + } + + protected void customizeContactFormForSuccessfulReservation(ContactAndTicketsForm contactForm) { + + } + + protected void ensureReservationIsComplete(String reservationId, ReservationFlowContext context) { + checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.COMPLETE, context); + } + + private void checkReservationExport(ReservationFlowContext context) { + Principal principal = mock(Principal.class); + Mockito.when(principal.getName()).thenReturn(context.userId); + // load all reservations + var now = LocalDate.now(clockProvider.getClock()); + var reservationsByEvent = exportManager.reservationsForInterval(now.minusDays(1), now, principal); + assertEquals(1, reservationsByEvent.size()); + assertEquals(1, reservationsByEvent.get(0).getReservations().size()); + assertEquals(1, reservationsByEvent.get(0).getReservations().get(0).getTickets().size()); + + // ensure that the filtering works as expected + reservationsByEvent = exportManager.reservationsForInterval(now.plusDays(1), now.plusDays(2), principal); + assertEquals(0, reservationsByEvent.size()); + + // ensure that we get error if the interval is wrong + var wrongFrom = now.plusDays(1); + assertThrows(IllegalArgumentException.class, () -> exportManager.reservationsForInterval(wrongFrom, now, principal)); + } + + static void insertExtension(ExtensionService extensionService, String path, Stream<String> events) throws IOException { + insertExtension(extensionService, path, true, true, events); + } + + static Stream<String> allEvents() { + return Arrays.stream(ExtensionEvent.values()).map(ee -> "'"+ee.name()+"'"); + } + + static void insertExtension(ExtensionService extensionService, String path, boolean async, boolean sync, Stream<String> events) throws IOException { + try (var extensionInputStream = requireNonNull(BaseReservationFlowTest.class.getResourceAsStream(path))) { + List<String> extensionStream = IOUtils.readLines(new InputStreamReader(extensionInputStream, StandardCharsets.UTF_8)); + String concatenation = String.join("\n", extensionStream).replace("EVENTS", events.collect(Collectors.joining(","))); + if (sync) { + extensionService.createOrUpdate(null, null, new Extension("-", "syncName", concatenation.replace("placeHolder", "false"), true)); + } + if (async) { + extensionService.createOrUpdate(null, null, new Extension("-", "asyncName", concatenation.replace("placeHolder", "true"), true)); + } + } + } + + protected void validateCheckInData(ReservationFlowContext context) { + + } + + protected void performAndValidatePayment(ReservationFlowContext context, + String reservationId, + int promoCodeId, + Runnable cleanupExtensionLog) { + ReservationInfo reservation; + var paymentForm = new PaymentForm(); + var handleResError = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), + new MockHttpServletRequest(), context.getPublicUser()); + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, handleResError.getStatusCode()); + + + paymentForm.setPrivacyPolicyAccepted(true); + paymentForm.setTermAndConditionsAccepted(true); + paymentForm.setPaymentProxy(PaymentProxy.OFFLINE); + paymentForm.setSelectedPaymentMethod(PaymentMethod.BANK_TRANSFER); + + // bank transfer does not have a transaction, it's created on confirmOverview call + var tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER"); + assertEquals(HttpStatus.NOT_FOUND, tStatus.getStatusCode()); + // + var promoCodeUsage = promoCodeRequestManager.retrieveDetailedUsage(promoCodeId, context.event.getId()); + assertTrue(promoCodeUsage.isEmpty()); + + var handleRes = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), + new MockHttpServletRequest(), context.getPublicUser()); + + assertEquals(HttpStatus.OK, handleRes.getStatusCode()); + + checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT, context); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER"); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + assertNotNull(tStatus.getBody()); + assertFalse(tStatus.getBody().isSuccess()); + + reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody(); + assertNotNull(reservation); + checkOrderSummary(reservation); + cleanupExtensionLog.run(); + validatePayment(context.event.getShortName(), reservationId, context); + + var extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); + + boolean online = containsOnlineTickets(context, reservationId); + assertEventLogged(extLogs, RESERVATION_CONFIRMED, online ? 12 : 10); + assertEventLogged(extLogs, CONFIRMATION_MAIL_CUSTOM_TEXT, online ? 12 : 10); + assertEventLogged(extLogs, TICKET_ASSIGNED, online ? 12 : 10); + if(online) { + assertEventLogged(extLogs, CUSTOM_ONLINE_JOIN_URL, 12); } + assertEventLogged(extLogs, TICKET_ASSIGNED_GENERATE_METADATA, online ? 12 : 10); + assertEventLogged(extLogs, TICKET_MAIL_CUSTOM_TEXT, online ? 12 : 10); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER"); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + assertNotNull(tStatus.getBody()); + assertTrue(tStatus.getBody().isSuccess()); + } + + protected void checkDiscountUsage(String reservationId, int promoCodeId, ReservationFlowContext context) { + var promoCodeUsage = promoCodeRequestManager.retrieveDetailedUsage(promoCodeId, context.event.getId()); + assertTrue(promoCodeUsage.isEmpty()); + } + protected void checkOrderSummary(ReservationInfo reservation) { + var orderSummary = reservation.getOrderSummary(); + assertTrue(orderSummary.isNotYetPaid()); + assertEquals("10.00", orderSummary.getTotalPrice()); + assertEquals("0.10", orderSummary.getTotalVAT()); + assertEquals("1.00", orderSummary.getVatPercentage()); + } + + private List<alfio.controller.api.v2.model.TicketCategory> retrieveCategories(ReservationFlowContext context) { + var response = requireNonNull(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null)); + return requireNonNull(response.getBody()).getTicketCategories(); } protected void performAdditionalTests(ReservationFlowContext reservationFlowContext) {} @@ -1180,7 +1442,8 @@ private TicketWithCategory testEncryptedCheckInPayload(Principal principal, Map<String, String> payload = checkInApiController.getOfflineEncryptedInfo(context.event.getShortName(), Collections.emptyList(), offlineIdentifiers, principal); assertEquals(1, payload.size()); TicketWithCategory ticketwc = ticketAndcheckInResult.getTicket(); - String ticketKey = ticketwc.hmacTicketInfo(context.event.getPrivateKey()); + String ticketKey = ticketwc.hmacTicketInfo(context.event.getPrivateKey(), true); + assertNotEquals(ticketKey, ticketwc.hmacTicketInfo(context.event.getPrivateKey(), false)); String hashedTicketKey = DigestUtils.sha256Hex(ticketKey); String encJson = payload.get(hashedTicketKey); assertNotNull(encJson); @@ -1228,7 +1491,7 @@ protected void testAddSubscription(ReservationFlowContext context, int numberOfT assertNotNull(reservation); assertEquals(reservationId, reservation.getId()); assertEquals(1, reservation.getTicketsByCategory().size()); - assertEquals(numberOfTickets, reservation.getTicketsByCategory().get(0).getTickets().size()); + assertEquals(numberOfTickets, reservation.getTicketsByCategory().get(0).tickets().size()); var contactForm = new ContactAndTicketsForm(); @@ -1239,7 +1502,7 @@ protected void testAddSubscription(ReservationFlowContext context, int numberOfT contactForm.setFirstName("full"); contactForm.setLastName("name"); - var tickets = reservation.getTicketsByCategory().get(0).getTickets().stream() + var tickets = reservation.getTicketsByCategory().get(0).tickets().stream() .map(t -> { var ticketForm = new UpdateTicketOwnerForm(); ticketForm.setFirstName("ticketfull"); @@ -1276,9 +1539,9 @@ protected void testAddSubscription(ReservationFlowContext context, int numberOfT assertTrue(reservation.getOrderSummary().isFree()); // assert that there is a row in the summary for the subscription - var summaryRowSubscription = reservation.getOrderSummary().getSummary().stream().filter(r -> r.getType() == SummaryRow.SummaryType.APPLIED_SUBSCRIPTION).findFirst(); + var summaryRowSubscription = reservation.getOrderSummary().getSummary().stream().filter(r -> r.type() == SummaryRow.SummaryType.APPLIED_SUBSCRIPTION).findFirst(); assertTrue(summaryRowSubscription.isPresent()); - assertEquals(numberOfTickets, summaryRowSubscription.get().getAmount()); + assertEquals(numberOfTickets, summaryRowSubscription.get().amount()); // proceed with the confirmation var paymentForm = new PaymentForm(); @@ -1309,12 +1572,16 @@ private void cleanupExtensionLog() { jdbcTemplate.update("delete from extension_log", Map.of()); } - private void assertEventLogged(List<ExtensionLog> extLog, ExtensionEvent event, int logSize) { + protected void assertEventLogged(List<ExtensionLog> extLog, ExtensionEvent event, int logSize) { assertEquals(logSize, extLog.size()); // each event logs exactly two logs assertTrue(extLog.stream().anyMatch(l -> l.getDescription().equals(event.name()))); } - private void checkStatus(String reservationId, + protected void assertEventLogged(List<ExtensionLog> extLog, ExtensionEvent event) { + assertTrue(extLog.stream().anyMatch(l -> l.getDescription().equals(event.name())), event.name() + " not found"); + } + + protected final void checkStatus(String reservationId, HttpStatus expectedHttpStatus, Boolean validated, TicketReservation.TicketReservationStatus reservationStatus, @@ -1331,7 +1598,7 @@ private void checkStatus(String reservationId, } } - private void validatePayment(String eventName, String reservationIdentifier, ReservationFlowContext context) { + protected void validatePayment(String eventName, String reservationIdentifier, ReservationFlowContext context) { Principal principal = mock(Principal.class); Mockito.when(principal.getName()).thenReturn(context.userId); var reservation = ticketReservationRepository.findReservationById(reservationIdentifier); @@ -1340,11 +1607,15 @@ private void validatePayment(String eventName, String reservationIdentifier, Res assertEquals(10, reservation.getVatCts()); assertEquals(0, reservation.getDiscountCts()); assertEquals(1, eventApiController.getPendingPayments(eventName).size()); - assertEquals("OK", eventApiController.confirmPayment(eventName, reservationIdentifier, principal)); + confirmPayment(eventName, reservationIdentifier, principal); assertEquals(0, eventApiController.getPendingPayments(eventName).size()); assertEquals(1000, eventRepository.getGrossIncome(context.event.getId())); } + private void confirmPayment(String eventName, String reservationIdentifier, Principal principal) { + assertEquals("OK", eventApiController.confirmPayment(eventName, reservationIdentifier, null, principal)); + } + private void checkCalendar(String eventName) { MockHttpServletResponse resIcal = new MockHttpServletResponse(); eventApiV2Controller.getCalendar(eventName, "en", null, null, resIcal); diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/BillingDocumentCreationIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/BillingDocumentCreationIntegrationTest.java index 2c8c848c25..bb60bdc287 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/BillingDocumentCreationIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/BillingDocumentCreationIntegrationTest.java @@ -20,6 +20,7 @@ import alfio.config.DataSourceConfiguration; import alfio.config.Initializer; import alfio.controller.api.ControllerConfiguration; +import alfio.controller.api.admin.AdminReservationApiController; import alfio.controller.api.v2.user.EventApiV2Controller; import alfio.controller.api.v2.user.ReservationApiV2Controller; import alfio.controller.form.ContactAndTicketsForm; @@ -30,10 +31,7 @@ import alfio.manager.EventManager; import alfio.manager.TicketReservationManager; import alfio.manager.user.UserManager; -import alfio.model.BillingDocument; -import alfio.model.Event; -import alfio.model.PurchaseContext; -import alfio.model.TicketCategory; +import alfio.model.*; import alfio.model.metadata.AlfioMetadata; import alfio.model.modification.DateTimeModification; import alfio.model.modification.TicketCategoryModification; @@ -44,14 +42,17 @@ import alfio.repository.BillingDocumentRepository; import alfio.repository.EventRepository; import alfio.repository.TicketCategoryRepository; +import alfio.repository.TicketRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpStatus; @@ -64,20 +65,22 @@ import org.springframework.web.context.request.ServletWebRequest; import java.math.BigDecimal; +import java.security.Principal; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class BillingDocumentCreationIntegrationTest extends BaseIntegrationTest { @Autowired @@ -104,6 +107,10 @@ class BillingDocumentCreationIntegrationTest extends BaseIntegrationTest { private TicketReservationManager ticketReservationManager; @Autowired private AdminReservationManager adminReservationManager; + @Autowired + private TicketRepository ticketRepository; + @Autowired + private AdminReservationApiController adminReservationApiController; private Event event; @@ -212,6 +219,36 @@ void creditReservationGeneratesCreditNote() { assertEquals(BillingDocument.Type.CREDIT_NOTE, billingDocuments.get(0).getType()); } + @Test + void deleteTicketGeneratesCreditNote() { + var reservationId = createReservation(form -> { + form.setInvoiceRequested(true); + form.setVatCountryCode("CH"); + form.setBillingAddressLine1("LINE 1"); + form.setBillingAddressCity("CITY"); + form.setBillingAddressZip("ZIP"); + }); + assertNotNull(reservationId); + var tickets = ticketRepository.findTicketsInReservation(reservationId); + var ticketIds = tickets.stream().map(Ticket::getId).collect(Collectors.toList()); + var principal = Mockito.mock(Principal.class); + when(principal.getName()).thenReturn(username); + var modification = new AdminReservationApiController.RemoveTicketsModification( + ticketIds, + Map.of(), + false, + true); + var result = adminReservationApiController.removeTickets(event.getShortName(), reservationId, modification,principal); + assertTrue(result.isSuccess()); + assertTrue(result.getData().isCreditNoteGenerated()); + var billingDocuments = billingDocumentRepository.findAllByReservationId(reservationId); + assertEquals(2, billingDocuments.size()); + assertEquals(2, ticketReservationManager.countBillingDocuments(event.getId())); + assertTrue(billingDocuments.stream().allMatch(bd -> bd.getStatus() == BillingDocument.Status.VALID)); + + assertEquals(BillingDocument.Type.CREDIT_NOTE, billingDocuments.get(0).getType()); + } + private String createReservation(Consumer<ContactAndTicketsForm> formCustomizer) { var categories = ticketCategoryRepository.findAllTicketCategories(event.getId()); assertFalse(categories.isEmpty()); @@ -236,8 +273,8 @@ private String createReservation(Consumer<ContactAndTicketsForm> formCustomizer) assertNotNull(resInfoRes.getBody()); var ticketsByCat = resInfoRes.getBody().getTicketsByCategory(); assertEquals(1, ticketsByCat.size()); - assertEquals(1, ticketsByCat.get(0).getTickets().size()); - var ticket = ticketsByCat.get(0).getTickets().get(0); + assertEquals(1, ticketsByCat.get(0).tickets().size()); + var ticket = ticketsByCat.get(0).tickets().get(0); var contactForm = new ContactAndTicketsForm(); @@ -273,7 +310,7 @@ private String createReservation(Consumer<ContactAndTicketsForm> formCustomizer) } private void confirmPaymentForReservation(String reservationId) { - ticketReservationManager.confirmOfflinePayment(event, reservationId, username); + ticketReservationManager.confirmOfflinePayment(event, reservationId, null, username); } private void expectInvoiceToBeGenerated(String reservationId) { diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java new file mode 100644 index 0000000000..e7d8e60119 --- /dev/null +++ b/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java @@ -0,0 +1,215 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v2.user.reservation; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import alfio.controller.api.ControllerConfiguration; +import alfio.controller.api.v2.user.ReservationApiV2Controller; +import alfio.controller.form.ContactAndTicketsForm; +import alfio.controller.form.UpdateTicketOwnerForm; +import alfio.extension.ExtensionService; +import alfio.manager.EventManager; +import alfio.manager.TicketReservationManager; +import alfio.manager.user.UserManager; +import alfio.model.Event; +import alfio.model.PriceContainer; +import alfio.model.TicketCategory; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.modification.DateTimeModification; +import alfio.model.modification.TicketCategoryModification; +import alfio.model.modification.TicketReservationModification; +import alfio.model.modification.TicketReservationWithOptionalCodeModification; +import alfio.repository.EventRepository; +import alfio.repository.TicketCategoryRepository; +import alfio.repository.TicketRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; +import alfio.test.util.IntegrationTestUtil; +import alfio.util.ClockProvider; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.validation.BeanPropertyBindingResult; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.*; + +import static alfio.controller.api.v2.user.reservation.BaseReservationFlowTest.*; +import static alfio.test.util.IntegrationTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; + +@AlfioIntegrationTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class CustomTaxPolicyIntegrationTest { + + private final OrganizationRepository organizationRepository; + private final UserManager userManager; + private final ExtensionService extensionService; + private final ClockProvider clockProvider; + private final EventManager eventManager; + private final EventRepository eventRepository; + private final ConfigurationRepository configurationRepository; + private final TicketReservationManager ticketReservationManager; + private final TicketCategoryRepository ticketCategoryRepository; + private final ReservationApiV2Controller reservationApiV2Controller; + private final TicketRepository ticketRepository; + + @Autowired + public CustomTaxPolicyIntegrationTest(OrganizationRepository organizationRepository, + EventManager eventManager, + EventRepository eventRepository, + UserManager userManager, + ClockProvider clockProvider, + ConfigurationRepository configurationRepository, + TicketCategoryRepository ticketCategoryRepository, + TicketRepository ticketRepository, + TicketReservationManager ticketReservationManager, + ReservationApiV2Controller reservationApiV2Controller, + ExtensionService extensionService) { + this.organizationRepository = organizationRepository; + this.userManager = userManager; + this.extensionService = extensionService; + this.clockProvider = clockProvider; + this.eventManager = eventManager; + this.eventRepository = eventRepository; + this.configurationRepository = configurationRepository; + this.ticketReservationManager = ticketReservationManager; + this.ticketCategoryRepository = ticketCategoryRepository; + this.reservationApiV2Controller = reservationApiV2Controller; + this.ticketRepository = ticketRepository; + } + + private ReservationFlowContext createContext(PriceContainer.VatStatus vatStatus) { + try { + IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); + insertExtension(extensionService, "/custom-tax-policy-extension.js", false, true, allEvents()); + List<TicketCategoryModification> categories = Arrays.asList( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, new BigDecimal("100.00"), false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "hidden", TicketCategory.TicketAccessType.INHERIT, 2, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, new BigDecimal("10.00"), true, "", true, URL_CODE_HIDDEN, null, null, null, null, 0, null, null, AlfioMetadata.empty()) + ); + Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository, List.of(), Event.EventFormat.IN_PERSON, vatStatus); + return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + void triggerCustomTaxPolicyTaxIncluded() { + var context = createContext(PriceContainer.VatStatus.INCLUDED); + var categories = ticketCategoryRepository.findAllTicketCategories(context.event.getId()); + assertEquals(2, categories.size()); + var ticketRequest = new TicketReservationModification(); + ticketRequest.setQuantity(2); + ticketRequest.setTicketCategoryId(categories.get(0).getId()); + + var request = List.of(new TicketReservationWithOptionalCodeModification(ticketRequest, Optional.empty())); + var reservationId = ticketReservationManager.createTicketReservation(context.event, request, List.of(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); + + var totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); + assertEquals(20000, totalPrice.priceWithVAT()); + + var tickets = ticketRepository.findTicketsInReservation(reservationId); + assertEquals(2, tickets.size()); + var firstUuid = tickets.get(0).getUuid(); + var secondUuid = tickets.get(1).getUuid(); + var contactAndTicketsForm = new ContactAndTicketsForm(); + contactAndTicketsForm.setFirstName("The"); + contactAndTicketsForm.setLastName("Customer"); + contactAndTicketsForm.setEmail("email@customer.com"); + contactAndTicketsForm.setTickets(Map.of( + firstUuid, updateTicketOwnerForm("example@example.org"), + secondUuid, updateTicketOwnerForm("example@example1.org") + )); + var bindingResult = new BeanPropertyBindingResult(contactAndTicketsForm, "form"); + var response = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactAndTicketsForm, bindingResult, null); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + // verify that first ticket has the expected tax settings + assertEquals(PriceContainer.VatStatus.CUSTOM_INCLUDED_EXEMPT, ticketRepository.findByUUID(firstUuid).getVatStatus()); + assertEquals(PriceContainer.VatStatus.INCLUDED, ticketRepository.findByUUID(secondUuid).getVatStatus()); + totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); + assertEquals(19901, totalPrice.priceWithVAT()); + assertEquals(19901, ticketReservationManager.findById(reservationId).orElseThrow().getFinalPriceCts()); + } + + @Test + void triggerCustomTaxPolicyTaxNotIncluded() { + var context = createContext(PriceContainer.VatStatus.NOT_INCLUDED); + var categories = ticketCategoryRepository.findAllTicketCategories(context.event.getId()); + assertEquals(2, categories.size()); + var ticketRequest = new TicketReservationModification(); + ticketRequest.setQuantity(2); + ticketRequest.setTicketCategoryId(categories.get(0).getId()); + + var request = List.of(new TicketReservationWithOptionalCodeModification(ticketRequest, Optional.empty())); + var reservationId = ticketReservationManager.createTicketReservation(context.event, request, List.of(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); + + var totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); + assertEquals(20200, totalPrice.priceWithVAT()); + + var tickets = ticketRepository.findTicketsInReservation(reservationId); + assertEquals(2, tickets.size()); + var firstUuid = tickets.get(0).getUuid(); + var secondUuid = tickets.get(1).getUuid(); + var contactAndTicketsForm = new ContactAndTicketsForm(); + contactAndTicketsForm.setFirstName("The"); + contactAndTicketsForm.setLastName("Customer"); + contactAndTicketsForm.setEmail("email@customer.com"); + contactAndTicketsForm.setTickets(Map.of( + firstUuid, updateTicketOwnerForm("example@example.org"), + secondUuid, updateTicketOwnerForm("example@example1.org") + )); + var bindingResult = new BeanPropertyBindingResult(contactAndTicketsForm, "form"); + var response = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactAndTicketsForm, bindingResult, null); + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isSuccess()); + // verify that first ticket has the expected tax settings + assertEquals(PriceContainer.VatStatus.CUSTOM_NOT_INCLUDED_EXEMPT, ticketRepository.findByUUID(firstUuid).getVatStatus()); + assertEquals(PriceContainer.VatStatus.NOT_INCLUDED, ticketRepository.findByUUID(secondUuid).getVatStatus()); + totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); + assertEquals(20100, totalPrice.priceWithVAT()); + assertEquals(20100, ticketReservationManager.findById(reservationId).orElseThrow().getFinalPriceCts()); + } + + private static UpdateTicketOwnerForm updateTicketOwnerForm(String email) { + var form = new UpdateTicketOwnerForm(); + form.setFirstName("first"); + form.setLastName("last"); + form.setEmail(email); + form.setUserLanguage("en"); + return form; + } +} diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/DiscountedReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/DiscountedReservationFlowIntegrationTest.java new file mode 100644 index 0000000000..f90462388a --- /dev/null +++ b/src/test/java/alfio/controller/api/v2/user/reservation/DiscountedReservationFlowIntegrationTest.java @@ -0,0 +1,210 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v2.user.reservation; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import alfio.controller.IndexController; +import alfio.controller.api.ControllerConfiguration; +import alfio.controller.api.admin.AdditionalServiceApiController; +import alfio.controller.api.admin.CheckInApiController; +import alfio.controller.api.admin.EventApiController; +import alfio.controller.api.admin.UsersApiController; +import alfio.controller.api.v1.AttendeeApiController; +import alfio.controller.api.v2.InfoApiController; +import alfio.controller.api.v2.TranslationsApiController; +import alfio.controller.api.v2.model.ReservationInfo; +import alfio.controller.api.v2.user.EventApiV2Controller; +import alfio.controller.api.v2.user.ReservationApiV2Controller; +import alfio.controller.api.v2.user.TicketApiV2Controller; +import alfio.extension.ExtensionService; +import alfio.manager.*; +import alfio.manager.user.UserManager; +import alfio.model.Event; +import alfio.model.TicketCategory; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.modification.DateTimeModification; +import alfio.model.modification.TicketCategoryModification; +import alfio.repository.*; +import alfio.repository.audit.ScanAuditRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.repository.user.OrganizationRepository; +import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; +import alfio.util.ClockProvider; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.security.Principal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.List; + +import static alfio.test.util.IntegrationTestUtil.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +@AlfioIntegrationTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class DiscountedReservationFlowIntegrationTest extends BaseReservationFlowTest { + + private final OrganizationRepository organizationRepository; + private final UserManager userManager; + + @Autowired + public DiscountedReservationFlowIntegrationTest(ConfigurationRepository configurationRepository, + EventManager eventManager, + EventRepository eventRepository, + EventStatisticsManager eventStatisticsManager, + TicketCategoryRepository ticketCategoryRepository, + TicketReservationRepository ticketReservationRepository, + EventApiController eventApiController, + TicketRepository ticketRepository, + TicketFieldRepository ticketFieldRepository, + AdditionalServiceApiController additionalServiceApiController, + SpecialPriceTokenGenerator specialPriceTokenGenerator, + SpecialPriceRepository specialPriceRepository, + CheckInApiController checkInApiController, + AttendeeApiController attendeeApiController, + UsersApiController usersApiController, + ScanAuditRepository scanAuditRepository, + AuditingRepository auditingRepository, + AdminReservationManager adminReservationManager, + TicketReservationManager ticketReservationManager, + InfoApiController infoApiController, + TranslationsApiController translationsApiController, + EventApiV2Controller eventApiV2Controller, + ReservationApiV2Controller reservationApiV2Controller, + TicketApiV2Controller ticketApiV2Controller, + IndexController indexController, + NamedParameterJdbcTemplate jdbcTemplate, + ExtensionLogRepository extensionLogRepository, + ExtensionService extensionService, + PollRepository pollRepository, + ClockProvider clockProvider, + NotificationManager notificationManager, + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + OrganizationRepository organizationRepository, + UserManager userManager, + ExportManager exportManager) { + super(configurationRepository, + eventManager, + eventRepository, + eventStatisticsManager, + ticketCategoryRepository, + ticketReservationRepository, + eventApiController, + ticketRepository, + ticketFieldRepository, + additionalServiceApiController, + specialPriceTokenGenerator, + specialPriceRepository, + checkInApiController, + attendeeApiController, + usersApiController, + scanAuditRepository, + auditingRepository, + adminReservationManager, + ticketReservationManager, + infoApiController, + translationsApiController, + eventApiV2Controller, + reservationApiV2Controller, + ticketApiV2Controller, + indexController, + jdbcTemplate, + extensionLogRepository, + extensionService, + pollRepository, + clockProvider, + notificationManager, + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); + this.organizationRepository = organizationRepository; + this.userManager = userManager; + } + + @Test + void discountedInPersonEvent() throws Exception { + super.testBasicFlow(() -> { + List<TicketCategoryModification> categories = Arrays.asList( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "hidden", TicketCategory.TicketAccessType.INHERIT, 2, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.ONE, true, "", true, URL_CODE_HIDDEN, null, null, null, null, 0, null, null, AlfioMetadata.empty()) + ); + Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); + return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", null, null, null, null, true, true); + }); + } + + @Override + protected void checkOrderSummary(ReservationInfo reservation) { + var orderSummary = reservation.getOrderSummary(); + assertTrue(orderSummary.isNotYetPaid()); + assertEquals("9.00", orderSummary.getTotalPrice()); + assertEquals("0.09", orderSummary.getTotalVAT()); + assertEquals("1.00", orderSummary.getVatPercentage()); + } + + protected void validatePayment(String eventName, String reservationIdentifier, ReservationFlowContext context) { + Principal principal = mock(Principal.class); + Mockito.when(principal.getName()).thenReturn(context.userId); + var reservation = ticketReservationRepository.findReservationById(reservationIdentifier); + assertEquals(900, reservation.getFinalPriceCts()); + assertEquals(1000, reservation.getSrcPriceCts()); + assertEquals(9, reservation.getVatCts()); + assertEquals(100, reservation.getDiscountCts()); + assertEquals(1, eventApiController.getPendingPayments(eventName).size()); + assertEquals("OK", eventApiController.confirmPayment(eventName, reservationIdentifier, null, principal)); + assertEquals(0, eventApiController.getPendingPayments(eventName).size()); + assertEquals(900, eventRepository.getGrossIncome(context.event.getId())); + } + + @Override + protected void checkDiscountUsage(String reservationId, int promoCodeId, ReservationFlowContext context) { + var promoCodeUsage = promoCodeRequestManager.retrieveDetailedUsage(promoCodeId, context.event.getId()); + assertEquals(1, promoCodeUsage.size()); + var usageDetail = promoCodeUsage.get(0); + assertEquals(PROMO_CODE, usageDetail.getPromoCode()); + assertEquals(1, usageDetail.getReservations().size()); + assertEquals(reservationId, usageDetail.getReservations().get(0).getId()); + assertEquals(1, usageDetail.getReservations().get(0).getTickets().size()); + } +} diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/HybridEventReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/HybridEventReservationFlowIntegrationTest.java index e33b09a830..430b44783d 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/HybridEventReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/HybridEventReservationFlowIntegrationTest.java @@ -44,6 +44,7 @@ import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.tuple.Pair; @@ -63,10 +64,9 @@ import static alfio.test.util.IntegrationTestUtil.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class HybridEventReservationFlowIntegrationTest extends BaseReservationFlowTest { private final OrganizationRepository organizationRepository; @@ -106,7 +106,11 @@ public HybridEventReservationFlowIntegrationTest(OrganizationRepository organiza ExtensionService extensionService, PollRepository pollRepository, NotificationManager notificationManager, - UserRepository userRepository) { + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + ExportManager exportManager) { super(configurationRepository, eventManager, eventRepository, @@ -138,7 +142,11 @@ public HybridEventReservationFlowIntegrationTest(OrganizationRepository organiza pollRepository, clockProvider, notificationManager, - userRepository); + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); this.organizationRepository = organizationRepository; this.userManager = userManager; } diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/OnlineEventReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/OnlineEventReservationFlowIntegrationTest.java index cb31702e09..635a5c5911 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/OnlineEventReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/OnlineEventReservationFlowIntegrationTest.java @@ -44,6 +44,7 @@ import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.tuple.Pair; @@ -63,11 +64,10 @@ import static alfio.test.util.IntegrationTestUtil.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class OnlineEventReservationFlowIntegrationTest extends BaseReservationFlowTest { +class OnlineEventReservationFlowIntegrationTest extends BaseReservationFlowTest { private final OrganizationRepository organizationRepository; private final UserManager userManager; @@ -106,7 +106,11 @@ public OnlineEventReservationFlowIntegrationTest(OrganizationRepository organiza ExtensionService extensionService, PollRepository pollRepository, NotificationManager notificationManager, - UserRepository userRepository) { + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + ExportManager exportManager) { super(configurationRepository, eventManager, eventRepository, @@ -138,7 +142,11 @@ public OnlineEventReservationFlowIntegrationTest(OrganizationRepository organiza pollRepository, clockProvider, notificationManager, - userRepository); + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); this.organizationRepository = organizationRepository; this.userManager = userManager; } @@ -155,7 +163,7 @@ private ReservationFlowContext createContext() { DESCRIPTION, BigDecimal.ONE, true, "", true, URL_CODE_HIDDEN, null, null, null, null, 0, null, null, AlfioMetadata.empty()) ); Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository, null, Event.EventFormat.ONLINE); - return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", null, null, null, null, false); + return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", null, null, null, null, false, false); } @Test diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowAuthenticatedUserIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowAuthenticatedUserIntegrationTest.java index 443147a744..d661a13e87 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowAuthenticatedUserIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowAuthenticatedUserIntegrationTest.java @@ -47,6 +47,7 @@ import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.tuple.Pair; @@ -69,11 +70,10 @@ import static alfio.test.util.IntegrationTestUtil.initEvent; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class ReservationFlowAuthenticatedUserIntegrationTest extends BaseReservationFlowTest { +class ReservationFlowAuthenticatedUserIntegrationTest extends BaseReservationFlowTest { private final OrganizationRepository organizationRepository; private final UserManager userManager; @@ -117,7 +117,11 @@ public ReservationFlowAuthenticatedUserIntegrationTest(OrganizationRepository or PollRepository pollRepository, NotificationManager notificationManager, UserRepository userRepository, - UserApiV2Controller publicUserApiController) { + UserApiV2Controller publicUserApiController, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + ExportManager exportManager) { super(configurationRepository, eventManager, eventRepository, @@ -149,7 +153,11 @@ public ReservationFlowAuthenticatedUserIntegrationTest(OrganizationRepository or pollRepository, clockProvider, notificationManager, - userRepository); + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); this.organizationRepository = organizationRepository; this.userManager = userManager; this.publicUserApiController = publicUserApiController; @@ -169,7 +177,7 @@ private ReservationFlowContext createContext() { Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); publicUserName = UUID.randomUUID().toString(); var userIdContainer = userRepository.create(publicUserName, UUID.randomUUID().toString(), "First", "Last", "email@example.org", true, User.Type.PUBLIC, null, ""); - return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", null, null, publicUserName, userIdContainer.getKey(), true); + return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", null, null, publicUserName, userIdContainer.getKey(), true, false); } @BeforeEach diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowContext.java b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowContext.java index d1fbdc1f99..711b836cae 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowContext.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowContext.java @@ -22,6 +22,7 @@ import java.security.Principal; import java.util.List; +import java.util.Map; import java.util.UUID; class ReservationFlowContext { @@ -32,17 +33,23 @@ class ReservationFlowContext { final String publicUsername; final Integer publicUserId; final boolean checkInStationsEnabled; + final boolean applyDiscount; private final Authentication authentication; + private final Map<String, String> additionalParams; ReservationFlowContext(Event event, String userId) { - this(event, userId, null, null, null, null, true); + this(event, userId, null, null, null, null, true, false, Map.of()); } ReservationFlowContext(Event event, String userId, UUID subscriptionId, String subscriptionPin) { - this(event, userId, subscriptionId, subscriptionPin, null, null, true); + this(event, userId, subscriptionId, subscriptionPin, null, null, true, false, Map.of()); } - ReservationFlowContext(Event event, String userId, UUID subscriptionId, String subscriptionPin, String publicUsername, Integer publicUserId, boolean checkInStationsEnabled) { + ReservationFlowContext(Event event, String userId, UUID subscriptionId, String subscriptionPin, String publicUsername, Integer publicUserId, boolean checkInStationsEnabled, boolean applyDiscount) { + this(event, userId, subscriptionId, subscriptionPin, publicUsername, publicUserId, checkInStationsEnabled, applyDiscount, Map.of()); + } + + ReservationFlowContext(Event event, String userId, UUID subscriptionId, String subscriptionPin, String publicUsername, Integer publicUserId, boolean checkInStationsEnabled, boolean applyDiscount, Map<String, String> additionalParams) { this.event = event; this.userId = userId; this.subscriptionId = subscriptionId; @@ -55,6 +62,8 @@ class ReservationFlowContext { this.authentication = null; } this.checkInStationsEnabled = checkInStationsEnabled; + this.applyDiscount = applyDiscount; + this.additionalParams = additionalParams; } Principal getPublicUser() { @@ -64,4 +73,8 @@ Principal getPublicUser() { Authentication getPublicAuthentication() { return authentication; } + + Map<String, String> getAdditionalParams() { + return additionalParams; + } } diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java index 614f3f2bfe..560ac23465 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java @@ -44,6 +44,7 @@ import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.tuple.Pair; @@ -59,23 +60,20 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Map; -import static alfio.test.util.IntegrationTestUtil.AVAILABLE_SEATS; -import static alfio.test.util.IntegrationTestUtil.initEvent; +import static alfio.test.util.IntegrationTestUtil.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class ReservationFlowIntegrationTest extends BaseReservationFlowTest { private final OrganizationRepository organizationRepository; private final UserManager userManager; - - private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); + private final CheckInManager checkInManager; @Autowired public ReservationFlowIntegrationTest(OrganizationRepository organizationRepository, @@ -111,7 +109,12 @@ public ReservationFlowIntegrationTest(OrganizationRepository organizationReposit ExtensionService extensionService, PollRepository pollRepository, NotificationManager notificationManager, - UserRepository userRepository) { + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + CheckInManager checkInManager, + ExportManager exportManager) { super(configurationRepository, eventManager, eventRepository, @@ -143,9 +146,14 @@ public ReservationFlowIntegrationTest(OrganizationRepository organizationReposit pollRepository, clockProvider, notificationManager, - userRepository); + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); this.organizationRepository = organizationRepository; this.userManager = userManager; + this.checkInManager = checkInManager; } private ReservationFlowContext createContext() { @@ -173,4 +181,16 @@ protected void performAdditionalTests(ReservationFlowContext context) { var event = context.event; BaseIntegrationTest.testTransferEventToAnotherOrg(event.getId(), event.getOrganizationId(), context.userId, jdbcTemplate); } + + @Override + protected void validateCheckInData(ReservationFlowContext context) { + var entries = checkInManager.retrieveLogEntries(context.event.getShortName(), context.userId); + assertEquals(1, entries.size()); + var entry = entries.get(0); + assertNotNull(entry.getTicketId()); + assertNotNull(entry.getAttendeeData()); + assertNotNull(entry.getAttendeeData().getMetadata()); + assertNotNull(entry.getAudit()); + entry.getAudit().forEach(audit -> assertEquals(context.userId + "_api", audit.username())); + } } diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowWithSubscriptionIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowWithSubscriptionIntegrationTest.java index 8327c3d8cf..efc6d354b4 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowWithSubscriptionIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowWithSubscriptionIntegrationTest.java @@ -50,6 +50,7 @@ import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import alfio.util.Json; @@ -79,11 +80,10 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class ReservationFlowWithSubscriptionIntegrationTest extends BaseReservationFlowTest { +class ReservationFlowWithSubscriptionIntegrationTest extends BaseReservationFlowTest { private final OrganizationRepository organizationRepository; private final UserManager userManager; @@ -134,7 +134,11 @@ public ReservationFlowWithSubscriptionIntegrationTest(OrganizationRepository org SubscriptionRepository subscriptionRepository, FileUploadManager fileUploadManager, UserRepository userRepository, - PlatformTransactionManager platformTransactionManager) { + PlatformTransactionManager platformTransactionManager, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + ExportManager exportManager) { super(configurationRepository, eventManager, eventRepository, @@ -166,7 +170,11 @@ public ReservationFlowWithSubscriptionIntegrationTest(OrganizationRepository org pollRepository, clockProvider, notificationManager, - userRepository); + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); this.organizationRepository = organizationRepository; this.userManager = userManager; this.subscriptionManager = subscriptionManager; @@ -203,6 +211,8 @@ void createContext() { var descriptor = subscriptionRepository.findOne(descriptorId).orElseThrow(); var subscriptionIdAndPin = confirmAndLinkSubscription(descriptor, event.getOrganizationId(), subscriptionRepository, ticketReservationRepository, maxEntries); this.subscriptionRepository.linkSubscriptionAndEvent(descriptorId, event.getId(), 0, event.getOrganizationId()); + var linkedSubscriptions = eventManager.getLinkedSubscriptionIds(event.getId(), event.getOrganizationId()); + assertEquals(List.of(descriptorId), linkedSubscriptions); this.context = new ReservationFlowContext(event, eventAndUser.getRight() + "_owner", subscriptionIdAndPin.getLeft(), subscriptionIdAndPin.getRight()); } @@ -216,7 +226,7 @@ void deleteEvent() { } @Test - public void inPersonEventWithSubscriptionUsingID() { + void inPersonEventWithSubscriptionUsingID() { var modifiedContext = new ReservationFlowContext(context.event, context.userId, context.subscriptionId, null); super.testAddSubscription(modifiedContext, 1); var eventInfo = eventStatisticsManager.getEventWithAdditionalInfo(context.event.getShortName(), context.userId); @@ -225,13 +235,13 @@ public void inPersonEventWithSubscriptionUsingID() { } @Test - public void inPersonEventWithSubscriptionUsingPin() { + void inPersonEventWithSubscriptionUsingPin() { super.testAddSubscription(context, 1); assertErrorWhenTransferToAnotherOrg(); } @Test - public void triggerMaxSubscriptionPerEvent() { + void triggerMaxSubscriptionPerEvent() { super.testAddSubscription(context, 1); var params = Map.of("subscriptionId", context.subscriptionId, "eventId", context.event.getId()); assertEquals(1, jdbcTemplate.queryForObject("select count(*) from tickets_reservation where subscription_id_fk = :subscriptionId and event_id_fk = :eventId", params, Integer.class)); @@ -247,7 +257,7 @@ public void triggerMaxSubscriptionPerEvent() { } @Test - public void triggerMaxUsage() { + void triggerMaxUsage() { assertEquals(2, subscriptionRepository.findSubscriptionById(context.subscriptionId).getMaxEntries()); jdbcTemplate.update("update subscription set max_entries = 1 where id = :id::uuid", Map.of("id", context.subscriptionId)); super.testAddSubscription(context, 1); @@ -314,7 +324,7 @@ private void assertErrorWhenTransferToAnotherOrg() { } @Test - public void testUpdateEventHeaderError() { + void testUpdateEventHeaderError() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/RetryConfirmationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/RetryConfirmationFlowIntegrationTest.java new file mode 100644 index 0000000000..53f0d5cfde --- /dev/null +++ b/src/test/java/alfio/controller/api/v2/user/reservation/RetryConfirmationFlowIntegrationTest.java @@ -0,0 +1,399 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v2.user.reservation; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import alfio.controller.IndexController; +import alfio.controller.api.ControllerConfiguration; +import alfio.controller.api.admin.AdditionalServiceApiController; +import alfio.controller.api.admin.CheckInApiController; +import alfio.controller.api.admin.EventApiController; +import alfio.controller.api.admin.UsersApiController; +import alfio.controller.api.v1.AttendeeApiController; +import alfio.controller.api.v2.InfoApiController; +import alfio.controller.api.v2.TranslationsApiController; +import alfio.controller.api.v2.model.ReservationInfo; +import alfio.controller.api.v2.user.EventApiV2Controller; +import alfio.controller.api.v2.user.ReservationApiV2Controller; +import alfio.controller.api.v2.user.TicketApiV2Controller; +import alfio.controller.form.ContactAndTicketsForm; +import alfio.controller.form.PaymentForm; +import alfio.controller.payment.api.stripe.StripePaymentWebhookController; +import alfio.extension.Extension; +import alfio.extension.ExtensionService; +import alfio.manager.*; +import alfio.manager.support.extension.ExtensionEvent; +import alfio.manager.system.AdminJobExecutor; +import alfio.manager.system.AdminJobManager; +import alfio.manager.system.AdminJobManagerInvoker; +import alfio.manager.user.UserManager; +import alfio.model.Event; +import alfio.model.ExtensionLog; +import alfio.model.TicketCategory; +import alfio.model.TicketReservation; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.metadata.TicketMetadataContainer; +import alfio.model.modification.DateTimeModification; +import alfio.model.modification.TicketCategoryModification; +import alfio.model.system.ConfigurationKeys; +import alfio.model.transaction.PaymentMethod; +import alfio.model.transaction.PaymentProxy; +import alfio.repository.*; +import alfio.repository.audit.ScanAuditRepository; +import alfio.repository.system.AdminJobQueueRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.repository.user.OrganizationRepository; +import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; +import alfio.util.ClockProvider; +import com.stripe.net.Webhook; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.validation.BeanPropertyBindingResult; + +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Stream; + +import static alfio.controller.api.v2.user.reservation.StripeReservationFlowIntegrationTest.WEBHOOK_SECRET; +import static alfio.test.util.IntegrationTestUtil.*; +import static alfio.util.HttpUtils.APPLICATION_JSON; +import static alfio.util.HttpUtils.APPLICATION_JSON_UTF8; +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.*; + +@AlfioIntegrationTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class RetryConfirmationFlowIntegrationTest extends BaseReservationFlowTest { + + private static final String TICKET_METADATA = "ticketMetadata"; + private static final String INVOICE_NUMBER_GENERATOR = "invoiceNumberGenerator"; + private final OrganizationRepository organizationRepository; + private final UserManager userManager; + private final AdminJobQueueRepository adminJobQueueRepository; + private final AdminJobManagerInvoker adminJobManagerInvoker; + private final StripePaymentWebhookController stripePaymentWebhookController; + + @Autowired + public RetryConfirmationFlowIntegrationTest(ConfigurationRepository configurationRepository, + EventManager eventManager, + EventRepository eventRepository, + EventStatisticsManager eventStatisticsManager, + TicketCategoryRepository ticketCategoryRepository, + TicketReservationRepository ticketReservationRepository, + EventApiController eventApiController, + TicketRepository ticketRepository, + TicketFieldRepository ticketFieldRepository, + AdditionalServiceApiController additionalServiceApiController, + SpecialPriceTokenGenerator specialPriceTokenGenerator, + SpecialPriceRepository specialPriceRepository, + CheckInApiController checkInApiController, + AttendeeApiController attendeeApiController, + UsersApiController usersApiController, + ScanAuditRepository scanAuditRepository, + AuditingRepository auditingRepository, + AdminReservationManager adminReservationManager, + TicketReservationManager ticketReservationManager, + InfoApiController infoApiController, + TranslationsApiController translationsApiController, + EventApiV2Controller eventApiV2Controller, + ReservationApiV2Controller reservationApiV2Controller, + TicketApiV2Controller ticketApiV2Controller, + IndexController indexController, + NamedParameterJdbcTemplate jdbcTemplate, + ExtensionLogRepository extensionLogRepository, + ExtensionService extensionService, + PollRepository pollRepository, + ClockProvider clockProvider, + NotificationManager notificationManager, + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + ExportManager exportManager, + OrganizationRepository organizationRepository, + UserManager userManager, + AdminJobQueueRepository adminJobQueueRepository, + AdminJobManager adminJobManager, + StripePaymentWebhookController stripePaymentWebhookController) { + super(configurationRepository, + eventManager, + eventRepository, + eventStatisticsManager, + ticketCategoryRepository, + ticketReservationRepository, + eventApiController, + ticketRepository, + ticketFieldRepository, + additionalServiceApiController, + specialPriceTokenGenerator, + specialPriceRepository, + checkInApiController, + attendeeApiController, + usersApiController, + scanAuditRepository, + auditingRepository, + adminReservationManager, + ticketReservationManager, + infoApiController, + translationsApiController, + eventApiV2Controller, + reservationApiV2Controller, + ticketApiV2Controller, + indexController, + jdbcTemplate, + extensionLogRepository, + extensionService, + pollRepository, + clockProvider, + notificationManager, + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); + this.organizationRepository = organizationRepository; + this.userManager = userManager; + this.adminJobQueueRepository = adminJobQueueRepository; + this.adminJobManagerInvoker = new AdminJobManagerInvoker(adminJobManager); + this.stripePaymentWebhookController = stripePaymentWebhookController; + } + + @BeforeEach + void setUp() { + configurationRepository.insert(ConfigurationKeys.STRIPE_ENABLE_SCA.name(), "true", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_PUBLIC_KEY.name(), "pk_test_123", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_SECRET_KEY.name(), "sk_test_123", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_WEBHOOK_PAYMENT_KEY.name(), WEBHOOK_SECRET, ""); + } + + private ReservationFlowContext createContext(boolean invoiceExtensionFailure) { + List<TicketCategoryModification> categories = Arrays.asList( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "hidden", TicketCategory.TicketAccessType.INHERIT, 2, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.ONE, true, "", true, URL_CODE_HIDDEN, null, null, null, null, 0, null, null, AlfioMetadata.empty()) + ); + Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); + return new CustomReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", invoiceExtensionFailure); + } + + @Test + void invoiceExtensionFailure() throws Exception { + insertOrUpdateExtension("/retry-reservation/fail-invoice-number-generator.js", INVOICE_NUMBER_GENERATOR, false); + insertOrUpdateExtension("/retry-reservation/success-ticket-metadata.js", TICKET_METADATA, false); + super.testBasicFlow(() -> createContext(true)); + } + + @Test + void metadataFailure() throws Exception { + insertOrUpdateExtension("/retry-reservation/success-invoice-number-generator.js", INVOICE_NUMBER_GENERATOR, false); + insertOrUpdateExtension("/retry-reservation/fail-ticket-metadata.js", TICKET_METADATA, false); + super.testBasicFlow(() -> createContext(false)); + } + + @Override + protected void ensureReservationIsComplete(String reservationId, ReservationFlowContext context) { + var ctx = (CustomReservationFlowContext) context; + checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.FINALIZING, context); + + // check that the IndexController is redirecting properly + var shortName = context.event.getShortName(); + var redirect = indexController.redirectEventToReservation(shortName, reservationId, null); + assertEquals("redirect:/event/"+shortName+"/reservation/"+reservationId+"/success", redirect); + + var reservation = ticketReservationRepository.findReservationById(reservationId); + if (ctx.invoiceExtensionFailure) { + // in this case the transaction must have been rolled back completely + assertNull(reservation.getInvoiceNumber()); + } else { + assertEquals("ABCD", reservation.getInvoiceNumber()); + } + // transaction must be present + var tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + assertTrue(requireNonNull(tStatus.getBody()).isSuccess()); + // check that the confirmation has been rescheduled + var now = ZonedDateTime.now(clockProvider.getClock()).plusSeconds(3); + var schedules = adminJobQueueRepository.loadPendingSchedules(Set.of(AdminJobExecutor.JobName.RETRY_RESERVATION_CONFIRMATION.name()), now); + assertEquals(1, schedules.size()); + + // fix the error, then trigger reschedule + if (ctx.invoiceExtensionFailure) { + insertOrUpdateExtension("/retry-reservation/success-invoice-number-generator.js", INVOICE_NUMBER_GENERATOR, true); + } else { + insertOrUpdateExtension("/retry-reservation/success-ticket-metadata.js", TICKET_METADATA, true); + } + adminJobManagerInvoker.invokeProcessPendingReservationsRetry(now); + checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.COMPLETE, context); + reservation = ticketReservationRepository.findReservationById(reservationId); + assertEquals("ABCD", reservation.getInvoiceNumber()); + ticketRepository.findTicketsInReservation(reservationId) + .forEach(t -> { + var metadata = ticketRepository.getTicketMetadata(t.getId()).getMetadataForKey(TicketMetadataContainer.GENERAL); + assertTrue(metadata.isPresent()); + assertEquals(t.getUuid(), metadata.get().getAttributes().get("uuid")); + }); + } + + @Override + protected void performAndValidatePayment(ReservationFlowContext context, + String reservationId, + int promoCodeId, + Runnable cleanupExtensionLog) { + ReservationInfo reservation; + var paymentForm = new PaymentForm(); + + paymentForm.setPrivacyPolicyAccepted(true); + paymentForm.setTermAndConditionsAccepted(true); + paymentForm.setPaymentProxy(PaymentProxy.STRIPE); + paymentForm.setSelectedPaymentMethod(PaymentMethod.CREDIT_CARD); + + var tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.NOT_FOUND, tStatus.getStatusCode()); + + // init payment + var initPaymentRes = reservationApiV2Controller.initTransaction(reservationId, PaymentMethod.CREDIT_CARD.name(), new LinkedMultiValueMap<>()); + assertEquals(HttpStatus.OK, initPaymentRes.getStatusCode()); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + + var resInfoResponse = reservationApiV2Controller.getReservationInfo(reservationId, null); + assertEquals(TicketReservation.TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT, Objects.requireNonNull(resInfoResponse.getBody()).getStatus()); + + // + var promoCodeUsage = promoCodeRequestManager.retrieveDetailedUsage(promoCodeId, context.event.getId()); + assertTrue(promoCodeUsage.isEmpty()); + + var handleRes = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), + new MockHttpServletRequest(), context.getPublicUser()); + + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, handleRes.getStatusCode()); + + cleanupExtensionLog.run(); + processWebHook(reservationId); + + // status must be FINALIZING + checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.FINALIZING, context); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + assertNotNull(tStatus.getBody()); + assertTrue(tStatus.getBody().isSuccess()); + + reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody(); + assertNotNull(reservation); + checkOrderSummary(reservation); + } + + private void processWebHook(String reservationId) { + try { + var resource = getClass().getResource("/transaction-json/stripe-success-valid.json"); + assertNotNull(resource); + var timestamp = String.valueOf(Webhook.Util.getTimeNow()); + var payload = Files.readString(Path.of(resource.toURI())).replaceAll("RESERVATION_ID", reservationId); + var signedHeader = "t=" + timestamp + ",v1=" +Webhook.Util.computeHmacSha256(WEBHOOK_SECRET, timestamp + "." + payload); + var httpRequest = new MockHttpServletRequest(); + httpRequest.setContent(payload.getBytes(StandardCharsets.UTF_8)); + httpRequest.setContentType(APPLICATION_JSON); + var response = stripePaymentWebhookController.receivePaymentConfirmation(signedHeader, httpRequest); + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(APPLICATION_JSON_UTF8, Objects.requireNonNull(response.getHeaders().getContentType()).toString()); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + @Override + protected void assertEventLogged(List<ExtensionLog> extLog, ExtensionEvent event, int logSize) { + super.assertEventLogged(extLog, event); + } + + @Override + protected void customizeContactFormForSuccessfulReservation(ContactAndTicketsForm contactForm) { + contactForm.setInvoiceRequested(true); + contactForm.setBillingAddressCity("City"); + contactForm.setBillingAddressCompany("Company"); + contactForm.setBillingAddressZip("0000"); + contactForm.setBillingAddressLine1("address"); + contactForm.setAddCompanyBillingDetails(true); + contactForm.setVatCountryCode("CH"); + contactForm.setVatNr("1234567"); + } + + private void insertOrUpdateExtension(String filePath, String name, boolean update) { + try (var extensionInputStream = requireNonNull(RetryConfirmationFlowIntegrationTest.class.getResourceAsStream(filePath))) { + List<String> extensionStream = IOUtils.readLines(new InputStreamReader(extensionInputStream, StandardCharsets.UTF_8)); + String concatenation = String.join("\n", extensionStream); + String previousPath = update ? "-" : null; + String previousName = update ? name : null; + extensionService.createOrUpdate(previousPath, previousName, new Extension("-", name, concatenation, true)); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + @Override + protected Stream<String> getExtensionEventsToRegister() { + return EnumSet.complementOf(EnumSet.of(ExtensionEvent.INVOICE_GENERATION, ExtensionEvent.TICKET_ASSIGNED_GENERATE_METADATA)) + .stream() + .map(ee -> "'"+ee.name()+"'"); + } + + @Override + protected void checkOrderSummary(ReservationInfo reservation) { + var orderSummary = reservation.getOrderSummary(); + assertFalse(orderSummary.isNotYetPaid()); + assertEquals("10.00", orderSummary.getTotalPrice()); + assertEquals("0.10", orderSummary.getTotalVAT()); + assertEquals("1.00", orderSummary.getVatPercentage()); + } + + static class CustomReservationFlowContext extends ReservationFlowContext { + + private final boolean invoiceExtensionFailure; + CustomReservationFlowContext(Event event, String userId, boolean invoiceExtensionFailure) { + super(event, userId); + this.invoiceExtensionFailure = invoiceExtensionFailure; + } + } +} diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/StripeReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/StripeReservationFlowIntegrationTest.java new file mode 100644 index 0000000000..0bd7450eeb --- /dev/null +++ b/src/test/java/alfio/controller/api/v2/user/reservation/StripeReservationFlowIntegrationTest.java @@ -0,0 +1,301 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.controller.api.v2.user.reservation; + +import alfio.TestConfiguration; +import alfio.config.DataSourceConfiguration; +import alfio.config.Initializer; +import alfio.controller.IndexController; +import alfio.controller.api.ControllerConfiguration; +import alfio.controller.api.admin.AdditionalServiceApiController; +import alfio.controller.api.admin.CheckInApiController; +import alfio.controller.api.admin.EventApiController; +import alfio.controller.api.admin.UsersApiController; +import alfio.controller.api.v1.AttendeeApiController; +import alfio.controller.api.v2.InfoApiController; +import alfio.controller.api.v2.TranslationsApiController; +import alfio.controller.api.v2.model.ReservationInfo; +import alfio.controller.api.v2.user.EventApiV2Controller; +import alfio.controller.api.v2.user.ReservationApiV2Controller; +import alfio.controller.api.v2.user.TicketApiV2Controller; +import alfio.controller.form.PaymentForm; +import alfio.controller.payment.api.stripe.StripePaymentWebhookController; +import alfio.extension.ExtensionService; +import alfio.manager.*; +import alfio.manager.user.UserManager; +import alfio.model.Event; +import alfio.model.TicketCategory; +import alfio.model.TicketReservation; +import alfio.model.metadata.AlfioMetadata; +import alfio.model.modification.DateTimeModification; +import alfio.model.modification.TicketCategoryModification; +import alfio.model.system.ConfigurationKeys; +import alfio.model.transaction.PaymentMethod; +import alfio.model.transaction.PaymentProxy; +import alfio.repository.*; +import alfio.repository.audit.ScanAuditRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.repository.user.OrganizationRepository; +import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; +import alfio.util.ClockProvider; +import com.stripe.net.Webhook; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.validation.BeanPropertyBindingResult; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static alfio.manager.support.extension.ExtensionEvent.*; +import static alfio.test.util.IntegrationTestUtil.*; +import static alfio.util.HttpUtils.APPLICATION_JSON; +import static alfio.util.HttpUtils.APPLICATION_JSON_UTF8; +import static org.junit.jupiter.api.Assertions.*; + +@AlfioIntegrationTest +@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) +@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +class StripeReservationFlowIntegrationTest extends BaseReservationFlowTest { + + public static final String WEBHOOK_SECRET = "WEBHOOK_SECRET"; + private static final String PAYLOAD_FILENAME = "payloadFilename"; + private final OrganizationRepository organizationRepository; + private final UserManager userManager; + private final StripePaymentWebhookController stripePaymentWebhookController; + + @Autowired + public StripeReservationFlowIntegrationTest(OrganizationRepository organizationRepository, + EventManager eventManager, + EventRepository eventRepository, + UserManager userManager, + ClockProvider clockProvider, + ConfigurationRepository configurationRepository, + EventStatisticsManager eventStatisticsManager, + TicketCategoryRepository ticketCategoryRepository, + TicketReservationRepository ticketReservationRepository, + EventApiController eventApiController, + TicketRepository ticketRepository, + TicketFieldRepository ticketFieldRepository, + AdditionalServiceApiController additionalServiceApiController, + SpecialPriceTokenGenerator specialPriceTokenGenerator, + SpecialPriceRepository specialPriceRepository, + CheckInApiController checkInApiController, + AttendeeApiController attendeeApiController, + UsersApiController usersApiController, + ScanAuditRepository scanAuditRepository, + AuditingRepository auditingRepository, + AdminReservationManager adminReservationManager, + TicketReservationManager ticketReservationManager, + InfoApiController infoApiController, + TranslationsApiController translationsApiController, + EventApiV2Controller eventApiV2Controller, + ReservationApiV2Controller reservationApiV2Controller, + TicketApiV2Controller ticketApiV2Controller, + IndexController indexController, + NamedParameterJdbcTemplate jdbcTemplate, + ExtensionLogRepository extensionLogRepository, + ExtensionService extensionService, + PollRepository pollRepository, + NotificationManager notificationManager, + UserRepository userRepository, + OrganizationDeleter organizationDeleter, + PromoCodeDiscountRepository promoCodeDiscountRepository, + PromoCodeRequestManager promoCodeRequestManager, + StripePaymentWebhookController stripePaymentWebhookController, + ExportManager exportManager) { + super(configurationRepository, + eventManager, + eventRepository, + eventStatisticsManager, + ticketCategoryRepository, + ticketReservationRepository, + eventApiController, + ticketRepository, + ticketFieldRepository, + additionalServiceApiController, + specialPriceTokenGenerator, + specialPriceRepository, + checkInApiController, + attendeeApiController, + usersApiController, + scanAuditRepository, + auditingRepository, + adminReservationManager, + ticketReservationManager, + infoApiController, + translationsApiController, + eventApiV2Controller, + reservationApiV2Controller, + ticketApiV2Controller, + indexController, + jdbcTemplate, + extensionLogRepository, + extensionService, + pollRepository, + clockProvider, + notificationManager, + userRepository, + organizationDeleter, + promoCodeDiscountRepository, + promoCodeRequestManager, + exportManager); + this.organizationRepository = organizationRepository; + this.userManager = userManager; + this.stripePaymentWebhookController = stripePaymentWebhookController; + } + + @BeforeEach + void init() { + configurationRepository.insert(ConfigurationKeys.STRIPE_ENABLE_SCA.name(), "true", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_PUBLIC_KEY.name(), "pk_test_123", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_SECRET_KEY.name(), "sk_test_123", ""); + configurationRepository.insert(ConfigurationKeys.STRIPE_WEBHOOK_PAYMENT_KEY.name(), WEBHOOK_SECRET, ""); + } + + private ReservationFlowContext createContext(String payloadFilename) { + List<TicketCategoryModification> categories = Arrays.asList( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "hidden", TicketCategory.TicketAccessType.INHERIT, 2, + new DateTimeModification(LocalDate.now(clockProvider.getClock()).minusDays(1), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()).plusDays(1), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.ONE, true, "", true, URL_CODE_HIDDEN, null, null, null, null, 0, null, null, AlfioMetadata.empty()) + ); + Pair<Event, String> eventAndUser = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository, null, Event.EventFormat.IN_PERSON); + return new ReservationFlowContext(eventAndUser.getLeft(), eventAndUser.getRight() + "_owner", null, null, null, null, true, false, Map.of(PAYLOAD_FILENAME, payloadFilename)); + } + + @ParameterizedTest + @ValueSource(strings = { + "/transaction-json/stripe-success-valid.json", + "/transaction-json/stripe-success-valid-v2022-11-15.json" + }) + void payWithStripe(String payloadFilename) throws Exception { + super.testBasicFlow(() -> createContext(payloadFilename)); + } + + @Override + protected void performAndValidatePayment(ReservationFlowContext context, + String reservationId, + int promoCodeId, + Runnable cleanupExtensionLog) { + ReservationInfo reservation; + var paymentForm = new PaymentForm(); + + paymentForm.setPrivacyPolicyAccepted(true); + paymentForm.setTermAndConditionsAccepted(true); + paymentForm.setPaymentProxy(PaymentProxy.STRIPE); + paymentForm.setSelectedPaymentMethod(PaymentMethod.CREDIT_CARD); + + var tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.NOT_FOUND, tStatus.getStatusCode()); + + // init payment + var initPaymentRes = reservationApiV2Controller.initTransaction(reservationId, PaymentMethod.CREDIT_CARD.name(), new LinkedMultiValueMap<>()); + assertEquals(HttpStatus.OK, initPaymentRes.getStatusCode()); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + + var resInfoResponse = reservationApiV2Controller.getReservationInfo(reservationId, null); + assertEquals(TicketReservation.TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT, Objects.requireNonNull(resInfoResponse.getBody()).getStatus()); + + // + var promoCodeUsage = promoCodeRequestManager.retrieveDetailedUsage(promoCodeId, context.event.getId()); + assertTrue(promoCodeUsage.isEmpty()); + + var handleRes = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), + new MockHttpServletRequest(), context.getPublicUser()); + + assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, handleRes.getStatusCode()); + + cleanupExtensionLog.run(); + processWebHook(context.getAdditionalParams().get(PAYLOAD_FILENAME), reservationId); + + checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.COMPLETE, context); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + assertNotNull(tStatus.getBody()); + assertTrue(tStatus.getBody().isSuccess()); + + reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody(); + assertNotNull(reservation); + checkOrderSummary(reservation); + + var extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); + + assertEventLogged(extLogs, RESERVATION_CONFIRMED); + assertEventLogged(extLogs, CONFIRMATION_MAIL_CUSTOM_TEXT); + assertEventLogged(extLogs, TICKET_ASSIGNED); + assertEventLogged(extLogs, TICKET_ASSIGNED_GENERATE_METADATA); + assertEventLogged(extLogs, TICKET_MAIL_CUSTOM_TEXT); + + tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, PaymentMethod.CREDIT_CARD.name()); + assertEquals(HttpStatus.OK, tStatus.getStatusCode()); + assertNotNull(tStatus.getBody()); + assertTrue(tStatus.getBody().isSuccess()); + } + + @Override + protected void checkOrderSummary(ReservationInfo reservation) { + var orderSummary = reservation.getOrderSummary(); + assertFalse(orderSummary.isNotYetPaid()); + assertEquals("10.00", orderSummary.getTotalPrice()); + assertEquals("0.10", orderSummary.getTotalVAT()); + assertEquals("1.00", orderSummary.getVatPercentage()); + } + + private void processWebHook(String filename, String reservationId) { + try { + var resource = getClass().getResource(filename); + assertNotNull(resource); + var timestamp = String.valueOf(Webhook.Util.getTimeNow()); + var payload = Files.readString(Path.of(resource.toURI())).replaceAll("RESERVATION_ID", reservationId); + var signedHeader = "t=" + timestamp + ",v1=" +Webhook.Util.computeHmacSha256(WEBHOOK_SECRET, timestamp + "." + payload); + var httpRequest = new MockHttpServletRequest(); + httpRequest.setContent(payload.getBytes(StandardCharsets.UTF_8)); + httpRequest.setContentType(APPLICATION_JSON); + var response = stripePaymentWebhookController.receivePaymentConfirmation(signedHeader, httpRequest); + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(APPLICATION_JSON_UTF8, Objects.requireNonNull(response.getHeaders().getContentType()).toString()); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/src/test/java/alfio/controller/support/FormattersTest.java b/src/test/java/alfio/controller/support/FormattersTest.java index 402ffae629..bedcb98423 100644 --- a/src/test/java/alfio/controller/support/FormattersTest.java +++ b/src/test/java/alfio/controller/support/FormattersTest.java @@ -24,6 +24,10 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; /** * This test checks if all the date patterns for all defined languages are correct, in order to spot errors at build @@ -46,4 +50,25 @@ void getFormattedDates() { FORMATTER_CODES.forEach(code -> Formatters.formatDateForLocale(now, code, messageSource, (a, b) -> Assertions.assertNotNull(b), cl, true)) ); } + + @Test + void getFormattedLink() { + var messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("alfio.i18n.public"); + var rendered = Formatters.applyCommonMark(Map.of("en", "[link](https://alf.io)")); + assertFalse(rendered.isEmpty()); + assertEquals(1, rendered.size()); + assertEquals("<p><a href=\"https://alf.io\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">link</a></p>", rendered.get("en").trim()); + } + + @Test + void getFormattedLinkWithAriaLabel() { + var messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("alfio.i18n.public"); + var message = messageSource.getMessage(Formatters.LINK_NEW_TAB_KEY, null, Locale.ENGLISH); + var rendered = Formatters.applyCommonMark(Map.of("en", "[link](https://alf.io)"), messageSource); + assertFalse(rendered.isEmpty()); + assertEquals(1, rendered.size()); + assertEquals("<p><a href=\"https://alf.io\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" aria-label=\"link "+message+"\">link</a></p>", rendered.get("en").trim()); + } } \ No newline at end of file diff --git a/src/test/java/alfio/controller/support/TemplateProcessorTest.java b/src/test/java/alfio/controller/support/TemplateProcessorTest.java index 96b5aa1d16..2d67d0b571 100644 --- a/src/test/java/alfio/controller/support/TemplateProcessorTest.java +++ b/src/test/java/alfio/controller/support/TemplateProcessorTest.java @@ -60,8 +60,8 @@ private void assertDimensionsUnder300x150(Pair<String, String> p) { FileUploadManager fileUploadManager = mock(FileUploadManager.class); when(fileUploadManager.findMetadata(e.getFileBlobId())).thenReturn(Optional.of(metadata)); TemplateProcessor.extractImageModel(e, fileUploadManager).ifPresent(imageData -> { - Assertions.assertTrue(imageData.getImageWidth() <= 300); - Assertions.assertTrue(imageData.getImageHeight() <= 150); + Assertions.assertTrue(imageData.imageWidth() <= 300); + Assertions.assertTrue(imageData.imageHeight() <= 150); }); } } \ No newline at end of file diff --git a/src/test/java/alfio/e2e/NormalFlowE2ETest.java b/src/test/java/alfio/e2e/NormalFlowE2ETest.java index 188af58b12..0622422988 100644 --- a/src/test/java/alfio/e2e/NormalFlowE2ETest.java +++ b/src/test/java/alfio/e2e/NormalFlowE2ETest.java @@ -16,20 +16,18 @@ */ package alfio.e2e; -import alfio.BaseTestConfiguration; import alfio.config.Initializer; -import alfio.util.BaseIntegrationTest; +import alfio.test.util.AlfioIntegrationTest; +import alfio.test.util.TestUtil; +import alfio.util.ActiveTravisProfileResolver; import alfio.util.ClockProvider; import alfio.util.HttpUtils; -import lombok.SneakyThrows; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.*; -import org.openqa.selenium.By; -import org.openqa.selenium.Keys; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; +import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.support.ui.WebDriverWait; @@ -52,7 +50,9 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -61,21 +61,19 @@ import static org.openqa.selenium.support.ui.ExpectedConditions.presenceOfElementLocated; /** - * For testing with browserstack you need: - * Enable the profiles e2e and travis - * -Dspring.profiles.active=e2e,travis + * For testing with browserstack you need to set the following VM run options: + * -Dspring.profiles.active=e2e,travis + * -De2e.server.url=https://master.test.alf.io + * -De2e.server.apikey= + * -De2e.browser=safari|firefox|chrome + * -Dbrowserstack.username= + * -Dbrowserstack.access.key= * - * And pass the following env var: - * - browserstack.username= - * - browserstack.access.key= - * - e2e.server.url= - * - e2e.server.apikey= - * - e2e.browser=ie11|safari|firefox|chrome */ -@ContextConfiguration(classes = { BaseTestConfiguration.class, NormalFlowE2ETest.E2EConfiguration.class }) -@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) +@ContextConfiguration(classes = { NormalFlowE2ETest.E2EConfiguration.class }) +@ActiveProfiles(value = {Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}, resolver = ActiveTravisProfileResolver.class) @SpringBootTest -public class NormalFlowE2ETest extends BaseIntegrationTest { +public class NormalFlowE2ETest { private static final Logger LOGGER = LoggerFactory.getLogger(NormalFlowE2ETest.class); private static final String JSON_BODY; @@ -98,11 +96,10 @@ public class NormalFlowE2ETest extends BaseIntegrationTest { @Autowired public NormalFlowE2ETest(List<BrowserWebDriver> webDrivers, - Environment environment, - ClockProvider clockProvider) { + Environment environment) { this.webDrivers = webDrivers; this.environment = environment; - this.clockProvider = clockProvider; + this.clockProvider = TestUtil.clockProvider(); } @BeforeEach @@ -150,14 +147,18 @@ public void destroy() throws Exception { } } + private static void clickWithJs(WebDriver driver, WebElement element) { + ((JavascriptExecutor) driver).executeScript("arguments[0].click();", element); + } + @Test @Timeout(value = 15L, unit = TimeUnit.MINUTES) - public void testFlow() { + public void testFlow() throws InterruptedException { for(var browserWebDriver : webDrivers) { var driver = browserWebDriver.driver; try { driver.navigate().to(eventUrl); - WebDriverWait wait = new WebDriverWait(driver, 10); + WebDriverWait wait = new WebDriverWait(driver, Duration.of(30, ChronoUnit.SECONDS)); wait.until(presenceOfElementLocated(By.cssSelector("div.markdown-content"))); page1TicketSelection(browserWebDriver); //wait until page is loaded @@ -168,7 +169,7 @@ public void testFlow() { wait.until(presenceOfElementLocated(By.cssSelector("h2[translate='reservation-page.title']"))); // page3Payment(browserWebDriver, wait); - WebElement fourthPageElem = new WebDriverWait(driver, 30).until(presenceOfElementLocated(By.cssSelector("div.attendees-data"))); + WebElement fourthPageElem = new WebDriverWait(driver, Duration.of(30, ChronoUnit.SECONDS)).until(presenceOfElementLocated(By.cssSelector("div.attendees-data"))); Assertions.assertNotNull(fourthPageElem); } finally { driver.quit(); @@ -186,6 +187,13 @@ private void page1TicketSelection(BrowserWebDriver browserWebDriver) { } + private static WebElement scrollTo(WebDriver driver, WebElement element) { + if (driver instanceof JavascriptExecutor js) { + js.executeScript("arguments[0].scrollIntoView();", element); + } + return element; + } + private void page2ContactDetails(BrowserWebDriver browserWebDriver, WebDriverWait wait) { var driver = browserWebDriver.driver; driver.findElement(By.id("first-name")).sendKeys("Test"); @@ -198,13 +206,16 @@ private void page2ContactDetails(BrowserWebDriver browserWebDriver, WebDriverWai selectElement(invoiceRequested.get(0), browserWebDriver); } - driver.findElement(By.cssSelector("label[for=invoiceTypePrivate]")).click(); + scrollTo(driver, driver.findElement(By.id("invoiceTypePrivate"))).click(); driver.findElement(By.id("billingAddressLine1")).sendKeys("Bahnhofstrasse 1"); driver.findElement(By.id("billingAddressZip")).sendKeys("8000"); driver.findElement(By.id("billingAddressCity")).sendKeys("Zürich"); - driver.findElement(By.cssSelector("ng-select[formcontrolname='vatCountryCode']")).click(); + Actions actions = new Actions(driver); + actions.moveToElement(driver.findElement(By.cssSelector("ng-select[formcontrolname='vatCountryCode']"))); + clickWithJs(driver, driver.findElement(By.cssSelector("ng-select[formcontrolname='vatCountryCode']"))); + driver.findElement(By.cssSelector("ng-select[formcontrolname='vatCountryCode'] input[id=vatCountry]")).sendKeys("switzerland"); wait.until(presenceOfElementLocated(By.cssSelector("ng-select[formcontrolname='vatCountryCode'] ng-dropdown-panel div[role=option]"))); selectElement(driver.findElement(By.cssSelector("ng-select[formcontrolname='vatCountryCode'] ng-dropdown-panel div[role=option]")), browserWebDriver, Keys.TAB); @@ -223,8 +234,8 @@ private void page2ContactDetails(BrowserWebDriver browserWebDriver, WebDriverWai driver.findElement(By.cssSelector("button[type=submit][translate='reservation-page.continue']")).sendKeys(Keys.RETURN); } - @SneakyThrows - private void page3Payment(BrowserWebDriver browserWebDriver, WebDriverWait wait) { + + private void page3Payment(BrowserWebDriver browserWebDriver, WebDriverWait wait) throws InterruptedException { var driver = browserWebDriver.driver; selectElement(driver.findElement(By.id("CREDIT_CARD-label")), browserWebDriver); wait.until(presenceOfElementLocated(By.cssSelector("#card-element iframe"))); @@ -244,8 +255,8 @@ private void page3Payment(BrowserWebDriver browserWebDriver, WebDriverWait wait) driver.findElement(By.cssSelector(".btn-success")).sendKeys(Keys.RETURN); } - @SneakyThrows - private void sendSlowInput(WebElement element, BrowserWebDriver browserWebDriver, CharSequence... strings) { + + private void sendSlowInput(WebElement element, BrowserWebDriver browserWebDriver, CharSequence... strings) throws InterruptedException { if(browserWebDriver.browser == BrowserWebDriver.Browser.SAFARI || browserWebDriver.browser == BrowserWebDriver.Browser.IE) { for (var str : strings) { element.sendKeys(str); @@ -265,7 +276,8 @@ private void selectElement(WebElement element, BrowserWebDriver driver, Keys key if(driver.browser == BrowserWebDriver.Browser.SAFARI) { element.sendKeys(keyToSend); } else { - element.click(); + // click with js... + clickWithJs(driver.driver, element); } } @@ -306,7 +318,6 @@ String browserStackUrl(Environment env) { private BrowserWebDriver build(String browser, URL url, String githubBuildNumber) { switch (browser) { - case "ie11": return new BrowserWebDriver(BrowserWebDriver.Browser.IE, buildRemoteDriver(url,"Windows", "10", "IE", "11", "testFlowIE11"+githubBuildNumber)); case "chrome": return new BrowserWebDriver(BrowserWebDriver.Browser.CHROME, buildRemoteDriver(url,"Windows", "10", "Chrome", "latest", "testFlowChrome"+githubBuildNumber)); case "firefox": return new BrowserWebDriver(BrowserWebDriver.Browser.FIREFOX, buildRemoteDriver(url,"Windows", "10", "Firefox", "latest", "testFlowFirefox"+githubBuildNumber)); case "safari": return new BrowserWebDriver(BrowserWebDriver.Browser.SAFARI, buildRemoteDriver(url,"OS X", "Catalina", "Safari", "13.1", "testFlowSafari"+githubBuildNumber)); diff --git a/src/test/java/alfio/job/executor/AssignTicketToSubscriberJobExecutorIntegrationTest.java b/src/test/java/alfio/job/executor/AssignTicketToSubscriberJobExecutorIntegrationTest.java index 2bf87604a3..5231d7bfc9 100644 --- a/src/test/java/alfio/job/executor/AssignTicketToSubscriberJobExecutorIntegrationTest.java +++ b/src/test/java/alfio/job/executor/AssignTicketToSubscriberJobExecutorIntegrationTest.java @@ -37,6 +37,7 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -62,10 +63,9 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class AssignTicketToSubscriberJobExecutorIntegrationTest { private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); diff --git a/src/test/java/alfio/job/executor/RetryFailedExtensionJobExecutorTest.java b/src/test/java/alfio/job/executor/RetryFailedExtensionJobExecutorTest.java index 6461181855..d24b68e725 100644 --- a/src/test/java/alfio/job/executor/RetryFailedExtensionJobExecutorTest.java +++ b/src/test/java/alfio/job/executor/RetryFailedExtensionJobExecutorTest.java @@ -36,6 +36,7 @@ import alfio.repository.system.AdminJobQueueRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.ClockProvider; import org.apache.commons.io.IOUtils; @@ -63,10 +64,9 @@ import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class RetryFailedExtensionJobExecutorTest { private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); diff --git a/src/test/java/alfio/manager/AdminReservationManagerIntegrationTest.java b/src/test/java/alfio/manager/AdminReservationManagerIntegrationTest.java index 05bbc4b2ef..a830c24c95 100644 --- a/src/test/java/alfio/manager/AdminReservationManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/AdminReservationManagerIntegrationTest.java @@ -19,6 +19,7 @@ import alfio.TestConfiguration; import alfio.config.DataSourceConfiguration; import alfio.config.Initializer; +import alfio.manager.payment.PaymentSpecification; import alfio.manager.user.UserManager; import alfio.model.*; import alfio.model.PurchaseContext.PurchaseContextType; @@ -26,12 +27,15 @@ import alfio.model.modification.*; import alfio.model.modification.AdminReservationModification.*; import alfio.model.result.Result; +import alfio.model.transaction.PaymentProxy; import alfio.repository.*; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; +import alfio.util.LocaleUtil; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -56,11 +60,10 @@ import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class AdminReservationManagerIntegrationTest extends BaseIntegrationTest { +class AdminReservationManagerIntegrationTest extends BaseIntegrationTest { @Autowired private AdminReservationManager adminReservationManager; @@ -86,12 +89,12 @@ public class AdminReservationManagerIntegrationTest extends BaseIntegrationTest private ConfigurationRepository configurationRepository; @BeforeEach - public void init() { + void init() { IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); } @Test - public void testReserveFromExistingCategory() { + void testReserveFromExistingCategory() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -101,7 +104,7 @@ public void testReserveFromExistingCategory() { } @Test - public void testReserveFromExistingMultipleCategories() { + void testReserveFromExistingMultipleCategories() { List<TicketCategoryModification> categories = Arrays.asList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -115,7 +118,7 @@ public void testReserveFromExistingMultipleCategories() { } @Test - public void testReserveFromExistingCategoryNotEnoughSeatsNotBounded() { + void testReserveFromExistingCategoryNotEnoughSeatsNotBounded() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -125,7 +128,7 @@ public void testReserveFromExistingCategoryNotEnoughSeatsNotBounded() { } @Test - public void testReserveExistingCategoryNotEnoughSeatsNotBoundedSoldOut() { + void testReserveExistingCategoryNotEnoughSeatsNotBoundedSoldOut() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -135,7 +138,7 @@ public void testReserveExistingCategoryNotEnoughSeatsNotBoundedSoldOut() { } @Test - public void testReserveFromExistingCategoryNotEnoughSeatsBounded() { + void testReserveFromExistingCategoryNotEnoughSeatsBounded() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -145,7 +148,7 @@ public void testReserveFromExistingCategoryNotEnoughSeatsBounded() { } @Test - public void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedBounded() { + void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedBounded() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -155,7 +158,7 @@ public void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedBound } @Test - public void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedNotBounded() { + void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedNotBounded() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -165,7 +168,7 @@ public void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedNotBo } @Test - public void testReserveFromNewCategory() { + void testReserveFromNewCategory() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -199,7 +202,7 @@ public void testReserveFromNewCategory() { } @Test - public void testReserveMixed() { + void testReserveMixed() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -245,7 +248,7 @@ public void testReserveMixed() { } @Test - public void testConfirmReservation() { + void testConfirmReservation() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -266,7 +269,7 @@ public void testConfirmReservation() { } @Test - public void testConfirmReservationSendConfirmationEmail() { + void testConfirmReservationSendConfirmationEmail() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -310,7 +313,7 @@ private Triple<Event, String, TicketReservation> performExistingCategoryTest(Lis if(reservedTickets > 0) { TicketReservationModification trm = new TicketReservationModification(); - trm.setAmount(reservedTickets); + trm.setQuantity(reservedTickets); trm.setTicketCategoryId(existingCategories.get(0).getId()); TicketReservationWithOptionalCodeModification r = new TicketReservationWithOptionalCodeModification(trm, Optional.empty()); ticketReservationManager.createTicketReservation(event, Collections.singletonList(r), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); diff --git a/src/test/java/alfio/manager/CheckInManagerIntegrationTest.java b/src/test/java/alfio/manager/CheckInManagerIntegrationTest.java index 63df114e00..52dabcfa9e 100644 --- a/src/test/java/alfio/manager/CheckInManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/CheckInManagerIntegrationTest.java @@ -32,6 +32,7 @@ import alfio.repository.TicketRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.ClockProvider; import org.apache.commons.lang3.time.DateUtils; @@ -51,16 +52,14 @@ import java.util.List; import java.util.Locale; import java.util.Optional; -import java.util.stream.Collectors; import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class CheckInManagerIntegrationTest { @Autowired @@ -114,7 +113,7 @@ void testReturnOnlyOnce() { var additionalService = eventManager.insertAdditionalService(event, additionalServiceRequest); var category = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(AVAILABLE_SEATS); + tr.setQuantity(AVAILABLE_SEATS); tr.setTicketCategoryId(category.getId()); var tickets = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -127,16 +126,15 @@ void testReturnOnlyOnce() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = ticketReservationManager.performPayment(specification, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); assertTrue(result.isSuccessful()); - ticketReservationManager.confirmOfflinePayment(event, reservationId, eventAndUser.getRight()); + ticketReservationManager.confirmOfflinePayment(event, reservationId, null, eventAndUser.getRight()); var returnedAdditionalServices = ticketReservationManager.findTicketsInReservation(reservationId).stream() - .filter(ticket -> !checkInManager.getAdditionalServicesForTicket(ticket).isEmpty()) - .collect(Collectors.toList()); + .filter(ticket -> !checkInManager.getAdditionalServicesForTicket(ticket).isEmpty()).toList(); // assertEquals(1, returnedAdditionalServices.size()); assertEquals((int) ticketRepository.findFirstTicketIdInReservation(reservationId).orElseThrow(), returnedAdditionalServices.get(0).getId()); diff --git a/src/test/java/alfio/manager/ConfigurationManagerIntegrationTest.java b/src/test/java/alfio/manager/ConfigurationManagerIntegrationTest.java index 9de6151764..877a00e2f0 100644 --- a/src/test/java/alfio/manager/ConfigurationManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/ConfigurationManagerIntegrationTest.java @@ -23,7 +23,9 @@ import alfio.manager.system.ConfigurationManager; import alfio.manager.user.UserManager; import alfio.model.Event; +import alfio.model.PurchaseContext; import alfio.model.TicketCategory; +import alfio.model.TicketReservation; import alfio.model.metadata.AlfioMetadata; import alfio.model.modification.ConfigurationModification; import alfio.model.modification.DateTimeModification; @@ -39,10 +41,12 @@ import alfio.repository.TicketCategoryRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; @@ -58,13 +62,14 @@ import static alfio.model.system.ConfigurationKeys.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class ConfigurationManagerIntegrationTest extends BaseIntegrationTest { +class ConfigurationManagerIntegrationTest extends BaseIntegrationTest { public static final String USERNAME = "test"; @@ -90,20 +95,25 @@ public class ConfigurationManagerIntegrationTest extends BaseIntegrationTest { private TicketCategoryRepository ticketCategoryRepository; @BeforeEach - public void prepareEnv() { + void prepareEnv() { //setup... organizationRepository.create("org", "org", "email@example.com", null, null); - Organization organization = organizationRepository.findByName("org").get(); + Organization organization = organizationRepository.findByName("org").orElseThrow(); - userManager.insertUser(organization.getId(), USERNAME, "test", "test", "test@example.com", Role.OWNER, User.Type.INTERNAL); + userManager.insertUser(organization.getId(), USERNAME, "test", "test", "test@example.com", Role.OWNER, User.Type.INTERNAL, null); Map<String, String> desc = new HashMap<>(); desc.put("en", "muh description"); desc.put("it", "muh description"); desc.put("de", "muh description"); - List<TicketCategoryModification> ticketsCategory = Collections.singletonList( + List<TicketCategoryModification> ticketsCategory = List.of( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 20, + new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), + new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), + Collections.singletonMap("en", "desc"), BigDecimal.TEN, false, "", false, null, null, + null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "second", TicketCategory.TicketAccessType.INHERIT, 20, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), Collections.singletonMap("en", "desc"), BigDecimal.TEN, false, "", false, null, null, @@ -121,33 +131,33 @@ public void prepareEnv() { } @Test - public void testPresentStringConfigValue() { + void testPresentStringConfigValue() { assertEquals(Optional.of("5"), configurationManager.getFor(MAX_AMOUNT_OF_TICKETS_BY_RESERVATION, ConfigurationLevel.event(event)).getValue()); } @Test - public void testEmptyStringConfigValue() { + void testEmptyStringConfigValue() { assertTrue(configurationManager.getFor(SMTP_PASSWORD, ConfigurationLevel.event(event)).getValue().isEmpty()); } @Test - public void testStringValueWithDefault() { + void testStringValueWithDefault() { assertEquals("5", configurationManager.getFor(MAX_AMOUNT_OF_TICKETS_BY_RESERVATION, ConfigurationLevel.event(event)).getRequiredValue()); assertEquals("-1", configurationManager.getFor(SMTP_PASSWORD, ConfigurationLevel.event(event)).getValueOrDefault("-1")); } @Test - public void testMissingConfigValue() { + void testMissingConfigValue() { assertThrows(IllegalArgumentException.class, () -> configurationManager.getFor(SMTP_PASSWORD, ConfigurationLevel.event(event)).getRequiredValue()); } @Test - public void testRequiredValue() { + void testRequiredValue() { assertEquals("5", configurationManager.getFor(MAX_AMOUNT_OF_TICKETS_BY_RESERVATION, ConfigurationLevel.event(event)).getRequiredValue()); } @Test - public void testIntValue() { + void testIntValue() { assertEquals(5, configurationManager.getFor(MAX_AMOUNT_OF_TICKETS_BY_RESERVATION, ConfigurationLevel.event(event)).getValueAsIntOrDefault(-1)); //missing value @@ -161,7 +171,7 @@ public void testIntValue() { } @Test - public void testBooleanValue() { + void testBooleanValue() { //missing value assertFalse(configurationManager.getFor(ALLOW_FREE_TICKETS_CANCELLATION, ConfigurationLevel.ticketCategory(event, ticketCategory.getId())).getValueAsBooleanOrDefault()); @@ -175,7 +185,7 @@ public void testBooleanValue() { } @Test - public void testOverrideMechanism() { + void testOverrideMechanism() { Organization organization = organizationRepository.findByName("org").orElseThrow(); @@ -208,7 +218,7 @@ public void testOverrideMechanism() { } @Test - public void testBasicConfigurationNotNeeded() { + void testBasicConfigurationNotNeeded() { configurationRepository.deleteByKey(ConfigurationKeys.BASE_URL.getValue()); configurationRepository.deleteByKey(ConfigurationKeys.SUPPORTED_LANGUAGES.getValue()); @@ -221,11 +231,11 @@ public void testBasicConfigurationNotNeeded() { } @Test - public void testSaveOnlyExistingConfiguration() { + void testSaveOnlyExistingConfiguration() { configurationRepository.insertOrganizationLevel(event.getOrganizationId(), ConfigurationKeys.BANK_ACCOUNT_NR.getValue(), "MY-ACCOUNT_NUMBER", "empty"); Configuration existing = configurationRepository.findByKeyAtOrganizationLevel(event.getOrganizationId(), ConfigurationKeys.BANK_ACCOUNT_NR.getValue()).orElseThrow(IllegalStateException::new); Map<ConfigurationKeys.SettingCategory, List<Configuration>> all = configurationManager.loadOrganizationConfig(event.getOrganizationId(), USERNAME); - List<Configuration> flatten = all.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toList()); + List<Configuration> flatten = all.entrySet().stream().flatMap(e -> e.getValue().stream()).toList(); List<ConfigurationModification> modified = flatten.stream().filter(c -> !c.getKey().equals(ConfigurationKeys.BANK_ACCOUNT_NR.getValue())).map(ConfigurationModification::fromConfiguration).collect(Collectors.toList()); modified.add(new ConfigurationModification(existing.getId(), existing.getKey(), "NEW-NUMBER")); configurationManager.saveAllOrganizationConfiguration(event.getOrganizationId(), modified, USERNAME); @@ -239,11 +249,11 @@ public void testSaveOnlyExistingConfiguration() { } @Test - public void testSaveOnlyValidConfiguration() { + void testSaveOnlyValidConfiguration() { configurationRepository.insertOrganizationLevel(event.getOrganizationId(), ConfigurationKeys.BANK_ACCOUNT_NR.getValue(), "MY-ACCOUNT_NUMBER", "empty"); Configuration existing = configurationRepository.findByKeyAtOrganizationLevel(event.getOrganizationId(), ConfigurationKeys.BANK_ACCOUNT_NR.getValue()).orElseThrow(IllegalStateException::new); Map<ConfigurationKeys.SettingCategory, List<Configuration>> all = configurationManager.loadOrganizationConfig(event.getOrganizationId(), USERNAME); - List<Configuration> flatten = all.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toList()); + List<Configuration> flatten = all.entrySet().stream().flatMap(e -> e.getValue().stream()).toList(); List<ConfigurationModification> modified = flatten.stream().filter(c -> !c.getKey().equals(ConfigurationKeys.BANK_ACCOUNT_NR.getValue()) && !c.getKey().equals(ConfigurationKeys.PARTIAL_RESERVATION_ID_LENGTH.getValue())).map(ConfigurationModification::fromConfiguration).collect(Collectors.toList()); modified.add(new ConfigurationModification(existing.getId(), existing.getKey(), "NEW-NUMBER")); modified.add(new ConfigurationModification(-1, ConfigurationKeys.PARTIAL_RESERVATION_ID_LENGTH.getValue(), "9")); @@ -263,7 +273,7 @@ public void testSaveOnlyValidConfiguration() { } @Test - public void testLoadOrganizationConfiguration() { + void testLoadOrganizationConfiguration() { Map<ConfigurationKeys.SettingCategory, List<Configuration>> orgConf = configurationManager.loadOrganizationConfig(event.getOrganizationId(), USERNAME); assertEquals(ConfigurationKeys.byPathLevel(ConfigurationPathLevel.ORGANIZATION).size(), orgConf.values().stream().mapToLong(Collection::size).sum()); String value = "MY-ACCOUNT_NUMBER"; @@ -274,13 +284,13 @@ public void testLoadOrganizationConfiguration() { } @Test - public void testBasicConfigurationNeeded() { + void testBasicConfigurationNeeded() { configurationRepository.deleteByKey(ConfigurationKeys.BASE_URL.getValue()); assertTrue(configurationManager.isBasicConfigurationNeeded()); } @Test - public void testSaveBooleanOptions() { + void testSaveBooleanOptions() { String ftcKey = ALLOW_FREE_TICKETS_CANCELLATION.getValue(); configurationRepository.insert(ftcKey, "false", "this should be updated to true"); ConfigurationModification ftc = new ConfigurationModification(configurationRepository.findByKey(ftcKey).getId(), ftcKey, "true"); @@ -317,7 +327,7 @@ public void testSaveBooleanOptions() { } @Test - public void testBulk() { + void testBulk() { Event event = eventManager.getSingleEvent("eventShortName", "test"); var res = configurationManager.getFor(Set.of(MAX_AMOUNT_OF_TICKETS_BY_RESERVATION, ENABLE_WAITING_QUEUE, ENABLE_WAITING_QUEUE_NOTIFICATION), ConfigurationLevel.event(event)); @@ -365,4 +375,37 @@ void testBaseUrl() { configurationRepository.insertEventLevel(event.getOrganizationId(), event.getId(), BASE_URL.getValue(), "https://test/", ""); assertEquals("https://test", configurationManager.baseUrl(event)); } + + @Test + void testSystemApiKeyGeneration() { + assertTrue(configurationRepository.findOptionalByKey(SYSTEM_API_KEY.name()).isEmpty()); + // force generation + var apiKey = configurationManager.retrieveSystemApiKey(false); + assertNotNull(apiKey); + assertFalse(apiKey.isEmpty()); + // retrieve again the same apiKey + var apiKey2 = configurationManager.retrieveSystemApiKey(false); + assertEquals(apiKey, apiKey2); + // force apiKey rotation + apiKey2 = configurationManager.retrieveSystemApiKey(true); + assertNotNull(apiKey2); + assertFalse(apiKey2.isEmpty()); + assertNotEquals(apiKey, apiKey2); + } + + @Test + void ensureNoErrorsWhenDeniedMethodsOptionIsEmpty() { + var categories = ticketCategoryRepository.findAllTicketCategories(event.getId()); + // insert empty value + configurationRepository.insertTicketCategoryLevel(event.getOrganizationId(), event.getId(), categories.get(0).getId(), PAYMENT_METHODS_BLACKLIST.name(), "", ""); + // try with single category + var deniedMethods = configurationManager.getBlacklistedMethodsForReservation(event, List.of(categories.get(0).getId())); + assertNotNull(deniedMethods); + assertTrue(deniedMethods.isEmpty()); + + // try with multiple categories + deniedMethods = configurationManager.getBlacklistedMethodsForReservation(event, categories.stream().map(TicketCategory::getId).collect(Collectors.toList())); + assertNotNull(deniedMethods); + assertTrue(deniedMethods.isEmpty()); + } } \ No newline at end of file diff --git a/src/test/java/alfio/manager/EventManagerIntegrationTest.java b/src/test/java/alfio/manager/EventManagerIntegrationTest.java index e1ddffd449..9708d8b7af 100644 --- a/src/test/java/alfio/manager/EventManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/EventManagerIntegrationTest.java @@ -31,6 +31,7 @@ import alfio.repository.*; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.time.DateUtils; @@ -54,10 +55,9 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class EventManagerIntegrationTest extends BaseIntegrationTest { @Autowired @@ -709,22 +709,50 @@ void testNewBoundedCategoryWithExistingBoundedAndPendingTicket() { @Test void deleteUnboundedTicketCategorySuccess() { List<TicketCategoryModification> cat = List.of( - new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS - 1, new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), - DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); + DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "second", TicketCategory.TicketAccessType.INHERIT, 1, + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); Pair<Event, String> pair = initEvent(cat, organizationRepository, userManager, eventManager, eventRepository); var event = pair.getLeft(); var categories = ticketCategoryRepository.findAllTicketCategories(event.getId()); - assertEquals(1, categories.size()); + assertEquals(2, categories.size()); int categoryId = categories.get(0).getId(); eventManager.deleteCategory(event.getShortName(), categoryId, pair.getRight()); - assertEquals(0, ticketCategoryRepository.findAllTicketCategories(event.getId()).size()); - assertEquals(AVAILABLE_SEATS, (int) ticketRepository.countFreeTicketsForUnbounded(event.getId())); + assertEquals(1, ticketCategoryRepository.findAllTicketCategories(event.getId()).size()); + assertEquals(AVAILABLE_SEATS - 1, (int) ticketRepository.countFreeTicketsForUnbounded(event.getId())); } @Test void deleteUnboundedTicketCategoryFailure() { + List<TicketCategoryModification> cat = List.of( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS - 1, + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "second", TicketCategory.TicketAccessType.INHERIT, 1, + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); + Pair<Event, String> pair = initEvent(cat, organizationRepository, userManager, eventManager, eventRepository); + var event = pair.getLeft(); + var tickets = ticketRepository.selectNotAllocatedTicketsForUpdate(event.getId(), 1, List.of(Ticket.TicketStatus.FREE.name())); + var categories = ticketCategoryRepository.findAllTicketCategories(event.getId()); + assertEquals(2, categories.size()); + int categoryId = categories.get(0).getId(); + String reservationId = UUID.randomUUID().toString(); + ticketReservationRepository.createNewReservation(reservationId, ZonedDateTime.now(clockProvider.getClock()), DateUtils.addDays(new Date(), 1), null, "en", event.getId(), event.getVat(), event.isVatIncluded(), event.getCurrency(), event.getOrganizationId(), null); + int result = ticketRepository.reserveTickets(reservationId, tickets, categories.get(0), "en", event.getVatStatus(), i -> null); + assertEquals(1, result); + assertThrows(IllegalStateException.class, () -> eventManager.deleteCategory(event.getShortName(), categoryId, pair.getRight())); + } + + @Test + void deleteUnboundedTicketCategoryFailureBecauseSingle() { List<TicketCategoryModification> cat = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), @@ -740,38 +768,46 @@ void deleteUnboundedTicketCategoryFailure() { ticketReservationRepository.createNewReservation(reservationId, ZonedDateTime.now(clockProvider.getClock()), DateUtils.addDays(new Date(), 1), null, "en", event.getId(), event.getVat(), event.isVatIncluded(), event.getCurrency(), event.getOrganizationId(), null); int result = ticketRepository.reserveTickets(reservationId, tickets, categories.get(0), "en", event.getVatStatus(), i -> null); assertEquals(1, result); - assertThrows(IllegalStateException.class, () -> eventManager.deleteCategory(event.getShortName(), categoryId, pair.getRight())); + assertThrows(IllegalArgumentException.class, () -> eventManager.deleteCategory(event.getShortName(), categoryId, pair.getRight())); } @Test void deleteBoundedTicketCategorySuccess() { List<TicketCategoryModification> cat = List.of( - new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS - 1, + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "second", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); Pair<Event, String> pair = initEvent(cat, organizationRepository, userManager, eventManager, eventRepository); var event = pair.getLeft(); var categories = ticketCategoryRepository.findAllTicketCategories(event.getId()); - assertEquals(1, categories.size()); + assertEquals(2, categories.size()); int categoryId = categories.get(0).getId(); eventManager.deleteCategory(event.getShortName(), categoryId, pair.getRight()); - assertEquals(0, ticketCategoryRepository.findAllTicketCategories(event.getId()).size()); + assertEquals(1, ticketCategoryRepository.findAllTicketCategories(event.getId()).size()); waitingQueueSubscriptionProcessor.handleWaitingTickets(); - assertEquals(AVAILABLE_SEATS, (int) ticketRepository.countFreeTicketsForUnbounded(event.getId())); + assertEquals(AVAILABLE_SEATS - 1, (int) ticketRepository.countFreeTicketsForUnbounded(event.getId())); } @Test void deleteBoundedTicketCategoryFailure() { - List<TicketCategoryModification> cat = Collections.singletonList( - new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, + List<TicketCategoryModification> cat = List.of( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS - 1, + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty()), + new TicketCategoryModification(null, "second", TicketCategory.TicketAccessType.INHERIT, 1, new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); Pair<Event, String> pair = initEvent(cat, organizationRepository, userManager, eventManager, eventRepository); var event = pair.getLeft(); var categories = ticketCategoryRepository.findAllTicketCategories(event.getId()); - assertEquals(1, categories.size()); + assertEquals(2, categories.size()); int categoryId = categories.get(0).getId(); var tickets = ticketRepository.selectTicketInCategoryForUpdate(event.getId(), categoryId, 1, List.of(Ticket.TicketStatus.FREE.name())); String reservationId = UUID.randomUUID().toString(); diff --git a/src/test/java/alfio/manager/EventNameManagerIntegrationTest.java b/src/test/java/alfio/manager/EventNameManagerIntegrationTest.java index dee8fc2243..d399379c95 100644 --- a/src/test/java/alfio/manager/EventNameManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/EventNameManagerIntegrationTest.java @@ -29,6 +29,7 @@ import alfio.repository.EventRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.junit.jupiter.api.BeforeEach; @@ -52,11 +53,10 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class EventNameManagerIntegrationTest extends BaseIntegrationTest { +class EventNameManagerIntegrationTest extends BaseIntegrationTest { @Autowired private EventRepository eventRepository; diff --git a/src/test/java/alfio/manager/ExtensionManagerIntegrationTest.java b/src/test/java/alfio/manager/ExtensionManagerIntegrationTest.java index d342ccfa0f..449e66c406 100644 --- a/src/test/java/alfio/manager/ExtensionManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/ExtensionManagerIntegrationTest.java @@ -36,6 +36,7 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -66,10 +67,9 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional class ExtensionManagerIntegrationTest { @Autowired diff --git a/src/test/java/alfio/manager/ExtensionManagerTest.java b/src/test/java/alfio/manager/ExtensionManagerTest.java index 02fb2716b2..03d40bb093 100644 --- a/src/test/java/alfio/manager/ExtensionManagerTest.java +++ b/src/test/java/alfio/manager/ExtensionManagerTest.java @@ -50,7 +50,7 @@ void setUp() { when(eventRepository.findOrganizationIdByEventId(1)).thenReturn(1); when(eventRepository.findById(1)).thenReturn(event); extensionService = mock(ExtensionService.class); - extensionManager = new ExtensionManager(extensionService, eventRepository, null, null, mock(ConfigurationManager.class), null); + extensionManager = new ExtensionManager(extensionService, eventRepository, null, null, mock(ConfigurationManager.class), null, null); } @Test diff --git a/src/test/java/alfio/manager/FileDownloadManagerIntegrationTest.java b/src/test/java/alfio/manager/FileDownloadManagerIntegrationTest.java index cc2bba4df6..6a742d0503 100644 --- a/src/test/java/alfio/manager/FileDownloadManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/FileDownloadManagerIntegrationTest.java @@ -56,9 +56,9 @@ public static void stopServer() { @Test public void testFileDownloadSuccess() { var file = new FileDownloadManager(httpClient).downloadFile("http://localhost:4242/test.txt"); - Assertions.assertEquals("text/plain; charset=utf-8", file.getType()); - Assertions.assertEquals("test.txt", file.getName()); - Assertions.assertEquals("Hello World!".length(), file.getFile().length); + Assertions.assertEquals("text/plain; charset=utf-8", file.type()); + Assertions.assertEquals("test.txt", file.name()); + Assertions.assertEquals("Hello World!".length(), file.file().length); } @Test diff --git a/src/test/java/alfio/manager/FileUploadManagerIntegrationTest.java b/src/test/java/alfio/manager/FileUploadManagerIntegrationTest.java index d2b51adde4..f6f3c005d2 100644 --- a/src/test/java/alfio/manager/FileUploadManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/FileUploadManagerIntegrationTest.java @@ -22,27 +22,27 @@ import alfio.config.Initializer; import alfio.model.FileBlobMetadata; import alfio.model.modification.UploadBase64FileModification; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import org.apache.commons.lang3.time.DateUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.Date; +import java.util.Objects; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class FileUploadManagerIntegrationTest extends BaseIntegrationTest { +@AlfioIntegrationTest +class FileUploadManagerIntegrationTest extends BaseIntegrationTest { @Autowired FileUploadManager fileUploadManager; @@ -50,7 +50,7 @@ public class FileUploadManagerIntegrationTest extends BaseIntegrationTest { private static final byte[] FILE = {1,2,3,4}; @Test - public void testInsert() { + void testInsert() { UploadBase64FileModification toInsert = new UploadBase64FileModification(); toInsert.setFile(FILE); toInsert.setName("myfile.txt"); @@ -76,7 +76,7 @@ public void testInsert() { @Test - public void testInsertImage() { + void testInsertImage() { UploadBase64FileModification toInsert = new UploadBase64FileModification(); toInsert.setFile(ONE_PIXEL_BLACK_GIF); toInsert.setName("image.gif"); @@ -95,7 +95,29 @@ public void testInsertImage() { } @Test - public void testFindMetadataNotPresent() { + void testFindMetadataNotPresent() { assertFalse(fileUploadManager.findMetadata("unknownid").isPresent()); } + + @Test + void testInsertResizedImage() throws IOException { + // Image credit: NASA, ESA, CSA, and STScI + try (var in = getClass().getResourceAsStream("/images/main_image_star-forming_region_carina_reduced.jpg")) { + UploadBase64FileModification toInsert = new UploadBase64FileModification(); + toInsert.setFile(Objects.requireNonNull(in).readAllBytes()); + toInsert.setName("image.jpg"); + toInsert.setType("image/jpeg"); + String id = fileUploadManager.insertFile(toInsert); + + Optional<FileBlobMetadata> metadata = fileUploadManager.findMetadata(id); + + assertTrue(metadata.isPresent()); + + assertEquals(String.valueOf(FileUploadManager.IMAGE_THUMB_MAX_WIDTH_PX), metadata.get().getAttributes().get("width")); + assertEquals("174", metadata.get().getAttributes().get("height")); + + fileUploadManager.cleanupUnreferencedBlobFiles(DateUtils.addDays(new Date(), 1)); + assertFalse(fileUploadManager.findMetadata(id).isPresent()); + } + } } diff --git a/src/test/java/alfio/manager/GroupManagerIntegrationTest.java b/src/test/java/alfio/manager/GroupManagerIntegrationTest.java index b53ecc60d5..69a95aaffb 100644 --- a/src/test/java/alfio/manager/GroupManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/GroupManagerIntegrationTest.java @@ -35,6 +35,7 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -57,11 +58,10 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class GroupManagerIntegrationTest extends BaseIntegrationTest { +class GroupManagerIntegrationTest extends BaseIntegrationTest { @Autowired private EventManager eventManager; @@ -93,7 +93,7 @@ public void setup() { } @Test - public void testLinkToEvent() { + void testLinkToEvent() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, @@ -121,7 +121,7 @@ public void testLinkToEvent() { assertTrue(groupManager.isAllowed("test@test.ch", event.getId(), categoryId)); TicketReservationModification ticketReservation = new TicketReservationModification(); - ticketReservation.setAmount(1); + ticketReservation.setQuantity(1); ticketReservation.setTicketCategoryId(categoryId); String reservationId = ticketReservationManager.createTicketReservation(event, Collections.singletonList(new TicketReservationWithOptionalCodeModification(ticketReservation, Optional.empty())), @@ -142,7 +142,7 @@ public void testLinkToEvent() { } @Test - public void testDuplicates() { + void testDuplicates() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, new DateTimeModification(LocalDate.now(ClockProvider.clock()).plusDays(1), LocalTime.now(ClockProvider.clock())), @@ -160,4 +160,30 @@ public void testDuplicates() { assertEquals("value.duplicate", items.getFirstErrorOrNull().getCode()); assertEquals("test@test.ch", items.getFirstErrorOrNull().getDescription()); } + + @Test + void testEscape() { + List<TicketCategoryModification> categories = Collections.singletonList( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, + new DateTimeModification(LocalDate.now(ClockProvider.clock()).plusDays(1), LocalTime.now(ClockProvider.clock())), + new DateTimeModification(LocalDate.now(ClockProvider.clock()).plusDays(2), LocalTime.now(ClockProvider.clock())), + DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); + Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); + Event event = pair.getKey(); + Group group = groupManager.createNew("test > 1", "This is a test < 1", event.getOrganizationId()); + assertNotNull(group); + assertEquals("This is a test < 1", group.getDescription()); + assertEquals("test > 1", group.getName()); + LinkedGroupModification modification = new LinkedGroupModification(null, group.getId(), event.getId(), null, LinkedGroup.Type.ONCE_PER_VALUE, LinkedGroup.MatchType.FULL, null); + LinkedGroup configuration = groupManager.createLink(group.getId(), event.getId(), modification); + assertNotNull(configuration); + Result<Integer> items = groupManager.insertMembers(group.getId(), List.of(new GroupMemberModification(null,"test@test.ch", "description <>"))); + assertTrue(items.isSuccess()); + var persistedGroup = groupManager.loadComplete(group.getId()).orElseThrow(); + assertEquals("description <>", persistedGroup.getItems().get(0).getDescription()); + groupManager.update(group.getId(), new GroupModification(group.getId(), "test > 1", "This is a test < 1", event.getOrganizationId(), List.of(new GroupMemberModification(null,"test@test.ch", "description <>")))); + persistedGroup = groupManager.loadComplete(group.getId()).orElseThrow(); + assertEquals("This is a test < 1", persistedGroup.getDescription()); + assertEquals("test > 1", persistedGroup.getName()); + } } \ No newline at end of file diff --git a/src/test/java/alfio/manager/I18nManagerIntegrationTest.java b/src/test/java/alfio/manager/I18nManagerIntegrationTest.java index 774a580cd8..c06986761b 100644 --- a/src/test/java/alfio/manager/I18nManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/I18nManagerIntegrationTest.java @@ -22,6 +22,7 @@ import alfio.manager.i18n.I18nManager; import alfio.manager.i18n.MessageSourceManager; import alfio.model.ContentLanguage; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -37,11 +38,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class I18nManagerIntegrationTest extends BaseIntegrationTest { +class I18nManagerIntegrationTest extends BaseIntegrationTest { private static final ZonedDateTime DATE = ZonedDateTime.of(1999, 2, 3, 4, 5, 6, 7, ZoneId.of("Europe/Zurich")); diff --git a/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java b/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java index 801e550cb6..95a374acb6 100644 --- a/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java @@ -41,6 +41,7 @@ import alfio.repository.TicketRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -70,11 +71,10 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class ReverseChargeManagerIntegrationTest extends BaseIntegrationTest { +class ReverseChargeManagerIntegrationTest extends BaseIntegrationTest { private final ClockProvider clockProvider; private final OrganizationRepository organizationRepository; @@ -152,10 +152,34 @@ void applyReverseChargeForOnlineTicket() { // we expect to find two rows for VAT: the first one for in-person (1%), the second one for online (0%) var rows = summary.getSummary(); assertEquals(3, rows.size()); - assertEquals(SummaryRow.SummaryType.TAX_DETAIL, rows.get(1).getType()); - assertEquals("0", rows.get(1).getTaxPercentage()); - assertEquals("", rows.get(1).getPrice()); - assertEquals("0.00", rows.get(1).getSubTotal()); + assertEquals(SummaryRow.SummaryType.TAX_DETAIL, rows.get(1).type()); + assertEquals("0", rows.get(1).taxPercentage()); + assertEquals("", rows.get(1).price()); + assertEquals("0.00", rows.get(1).subTotal()); + } + + @Test + void disableTaxForOneTicketCategory() { + configurationManager.saveConfig(Configuration.from(event, ENABLE_EU_VAT_DIRECTIVE), "false"); + var inPersonCategory = ticketCategoryRepository.findAllTicketCategories(event.getId()).stream() + .filter(tc -> tc.getTicketAccessType() == TicketCategory.TicketAccessType.ONLINE) + .findFirst() + .orElseThrow(); + configurationRepository.insertTicketCategoryLevel(event.getOrganizationId(), event.getId(), inPersonCategory.getId(), APPLY_TAX_TO_CATEGORY.name(), "false", ""); + + var reservation = createReservation(); + var summary = reservation.getOrderSummary(); + // 20 + 1.98 = 21.98 + assertEquals("0.20", summary.getTotalVAT()); + assertEquals(2198, summary.getPriceInCents()); + + // we expect to find two rows for VAT: the first one for in-person (1%), the second one for online (0%) + var rows = summary.getSummary(); + assertEquals(3, rows.size()); + assertEquals(SummaryRow.SummaryType.TAX_DETAIL, rows.get(1).type()); + assertEquals("0", rows.get(1).taxPercentage()); + assertEquals("", rows.get(1).price()); + assertEquals("0.00", rows.get(1).subTotal()); } @Test @@ -218,11 +242,11 @@ private ReservationInfo createReservation(String id, boolean requestInvoice) { var form = new ReservationForm(); var first = new TicketReservationModification(); - first.setAmount(2); + first.setQuantity(2); first.setTicketCategoryId(categories.get(0).getId()); var second = new TicketReservationModification(); - second.setAmount(2); + second.setQuantity(2); second.setTicketCategoryId(categories.get(1).getId()); form.setReservation(List.of(first, second)); diff --git a/src/test/java/alfio/manager/SubscriptionManagerIntegrationTest.java b/src/test/java/alfio/manager/SubscriptionManagerIntegrationTest.java index 017817d8fc..fbf3b90833 100644 --- a/src/test/java/alfio/manager/SubscriptionManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/SubscriptionManagerIntegrationTest.java @@ -35,6 +35,7 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -62,11 +63,10 @@ import static alfio.test.util.IntegrationTestUtil.*; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class SubscriptionManagerIntegrationTest { +class SubscriptionManagerIntegrationTest { @Autowired ConfigurationRepository configurationRepository; @@ -295,6 +295,24 @@ void updatePrice() { assertEquals(10, count); } + @Test + void deactivate() { + int orgId = event.getOrganizationId(); + assertTrue(subscriptionManager.findAll(orgId).isEmpty()); + var request = buildSubscriptionDescriptor(orgId, null, new BigDecimal("100"), 10); + + var optionalDescriptorId = subscriptionManager.createSubscriptionDescriptor(request); + assertTrue(optionalDescriptorId.isPresent()); + var descriptorId = optionalDescriptorId.get(); + assertTrue(subscriptionRepository.existsById(descriptorId)); + var result = subscriptionManager.deactivateDescriptor(orgId, descriptorId); + assertTrue(result.isSuccess()); + assertFalse(subscriptionRepository.existsById(descriptorId)); + assertTrue(subscriptionRepository.findAllActiveAndPublic(ZonedDateTime.now(ClockProvider.clock()), null).isEmpty()); + assertTrue(subscriptionManager.loadActiveSubscriptionDescriptors(orgId).isEmpty()); + assertTrue(subscriptionRepository.findAllWithStatistics(orgId).isEmpty()); + } + private SubscriptionDescriptorModification buildSubscriptionDescriptor(int orgId, UUID id, BigDecimal price) { return buildSubscriptionDescriptor(orgId, id, price, 42); } diff --git a/src/test/java/alfio/manager/TicketReservationManagerConcurrentTest.java b/src/test/java/alfio/manager/TicketReservationManagerConcurrentTest.java index e10dfba654..bbf7bc2d4e 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerConcurrentTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerConcurrentTest.java @@ -31,6 +31,7 @@ import alfio.repository.SpecialPriceRepository; import alfio.repository.TicketCategoryRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.AfterEach; @@ -60,10 +61,10 @@ import static alfio.test.util.TestUtil.clockProvider; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest() +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -public class TicketReservationManagerConcurrentTest { +class TicketReservationManagerConcurrentTest { private static final String ACCESS_CODE = "MY_ACCESS_CODE"; @@ -114,21 +115,21 @@ public void setUp() { firstCategoryId = ticketCategoryRepository.findAllTicketCategories(eventId).get(0).getId(); specialPriceTokenGenerator.generatePendingCodesForCategory(firstCategoryId); - promoCodeDiscountRepository.addPromoCode(ACCESS_CODE, eventId, event.getOrganizationId(), ZonedDateTime.now(clockProvider().getClock()), ZonedDateTime.now(clockProvider().getClock()).plusDays(1), 0, PromoCodeDiscount.DiscountType.NONE, null, 100, null, null, PromoCodeDiscount.CodeType.ACCESS, firstCategoryId); + promoCodeDiscountRepository.addPromoCode(ACCESS_CODE, eventId, event.getOrganizationId(), ZonedDateTime.now(clockProvider().getClock()), ZonedDateTime.now(clockProvider().getClock()).plusDays(1), 0, PromoCodeDiscount.DiscountType.NONE, null, 100, null, null, PromoCodeDiscount.CodeType.ACCESS, firstCategoryId, null); promoCodeDiscount = promoCodeDiscountRepository.findPublicPromoCodeInEventOrOrganization(eventId, ACCESS_CODE).orElseThrow(); return null; }); } @Test - public void testConcurrentAccessCode() throws InterruptedException { + void testConcurrentAccessCode() throws InterruptedException { var pool = Executors.newFixedThreadPool(AVAILABLE_SEATS); var callableList = new ArrayList<Callable<List<SpecialPrice>>>(); for (int i = 0; i < AVAILABLE_SEATS; i++) { callableList.add(() -> { return transactionTemplate.execute(tx -> { TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(1); + tr.setQuantity(1); tr.setTicketCategoryId(firstCategoryId); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); return ticketReservationManager.reserveTokensForAccessCode(mod, promoCodeDiscount); @@ -153,9 +154,9 @@ public void testConcurrentAccessCode() throws InterruptedException { } @Test - public void testExpirationDuringReservation() { + void testExpirationDuringReservation() { TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(1); + tr.setQuantity(1); tr.setTicketCategoryId(firstCategoryId); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); diff --git a/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java b/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java index bd2acd0969..b9cd49b000 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java @@ -21,6 +21,8 @@ import alfio.config.Initializer; import alfio.manager.payment.PaymentSpecification; import alfio.manager.support.PaymentResult; +import alfio.manager.support.reservation.NotEnoughTicketsException; +import alfio.manager.support.reservation.TooManyTicketsForDiscountCodeException; import alfio.manager.user.UserManager; import alfio.model.*; import alfio.model.metadata.AlfioMetadata; @@ -30,6 +32,7 @@ import alfio.repository.*; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -61,11 +64,10 @@ import static alfio.test.util.IntegrationTestUtil.initEvent; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class TicketReservationManagerIntegrationTest extends BaseIntegrationTest { +class TicketReservationManagerIntegrationTest extends BaseIntegrationTest { static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); private static final String ACCESS_CODE = "MYACCESSCODE"; @@ -107,12 +109,12 @@ public class TicketReservationManagerIntegrationTest extends BaseIntegrationTest private NamedParameterJdbcTemplate jdbcTemplate; @BeforeEach - public void ensureConfiguration() { + void ensureConfiguration() { IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository); } @Test - public void testPriceIsOverridden() { + void testPriceIsOverridden() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -122,7 +124,7 @@ public void testPriceIsOverridden() { Event event = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository).getKey(); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(2); + tr.setQuantity(2); TicketCategory category = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); tr.setTicketCategoryId(category.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -136,7 +138,7 @@ public void testPriceIsOverridden() { } @Test - public void testTicketSelection() { + void testTicketSelection() { List<TicketCategoryModification> categories = List.of( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -157,11 +159,11 @@ public void testTicketSelection() { assertEquals(0, eventStatisticsManager.loadModifiedTickets(event.getId(), unbounded.getId(), 0, null).size()); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(10); + tr.setQuantity(10); tr.setTicketCategoryId(bounded.getId()); TicketReservationModification tr2 = new TicketReservationModification(); - tr2.setAmount(9); + tr2.setQuantity(9); tr2.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -186,7 +188,7 @@ public void testTicketSelection() { assertEquals(0, ticketReservationManager.getPendingPayments(event.getShortName()).size()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, totalPrice.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, totalPrice.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); @@ -220,25 +222,25 @@ public void testTicketSelection() { //------------------- TicketReservationModification trForDelete = new TicketReservationModification(); - trForDelete.setAmount(1); + trForDelete.setQuantity(1); trForDelete.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification modForDelete = new TicketReservationWithOptionalCodeModification(trForDelete, Optional.empty()); String reservationId2 = ticketReservationManager.createTicketReservation(event, Collections.singletonList(modForDelete), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); - PaymentSpecification specification2 = new PaymentSpecification(reservationId2, null, totalPrice.getPriceWithVAT(), + PaymentSpecification specification2 = new PaymentSpecification(reservationId2, null, totalPrice.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult confirm2 = ticketReservationManager.performPayment(specification2, totalPrice, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); assertTrue(confirm2.isSuccessful()); - ticketReservationManager.deleteOfflinePayment(event, reservationId2, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationId2, false, false, false, null); assertFalse(ticketReservationManager.findById(reservationId2).isPresent()); } @Test - public void deferredOfflinePayment() { + void deferredOfflinePayment() { // enable deferred payment configurationRepository.insert(DEFERRED_BANK_TRANSFER_ENABLED.name(), "true", ""); @@ -254,7 +256,7 @@ public void deferredOfflinePayment() { TicketReservationModification trForDeferred = new TicketReservationModification(); - trForDeferred.setAmount(1); + trForDeferred.setQuantity(1); trForDeferred.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification modForDeferred = new TicketReservationWithOptionalCodeModification(trForDeferred, Optional.empty()); String reservationId = ticketReservationManager.createTicketReservation(event, Collections.singletonList(modForDeferred), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); @@ -263,7 +265,7 @@ public void deferredOfflinePayment() { TotalPrice totalPrice = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specificationDeferred = new PaymentSpecification(reservationId, null, totalPrice.getPriceWithVAT(), + PaymentSpecification specificationDeferred = new PaymentSpecification(reservationId, null, totalPrice.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); @@ -274,11 +276,11 @@ public void deferredOfflinePayment() { assertEquals(TicketReservation.TicketReservationStatus.DEFERRED_OFFLINE_PAYMENT, status); // confirm deferred payment - ticketReservationManager.confirmOfflinePayment(event, reservationId, null); + ticketReservationManager.confirmOfflinePayment(event, reservationId, null, null); reservationId = ticketReservationManager.createTicketReservation(event, Collections.singletonList(modForDeferred), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); - specificationDeferred = new PaymentSpecification(reservationId, null, totalPrice.getPriceWithVAT(), + specificationDeferred = new PaymentSpecification(reservationId, null, totalPrice.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); @@ -286,18 +288,18 @@ public void deferredOfflinePayment() { assertTrue(confirm.isSuccessful()); try { - ticketReservationManager.deleteOfflinePayment(event, reservationId, false, true, null); + ticketReservationManager.deleteOfflinePayment(event, reservationId, false, true, false, null); fail("Credit should not be enabled for deferred payments"); } catch (IllegalArgumentException ex) { // do nothing, because this is the expected behavior } - ticketReservationManager.deleteOfflinePayment(event, reservationId, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationId, false, false, false, null); assertFalse(ticketReservationManager.findById(reservationId).isPresent()); } @Test - public void testTicketWithDiscount() { + void testTicketWithDiscount() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -308,13 +310,13 @@ public void testTicketWithDiscount() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new); //promo code at event level - eventManager.addPromoCode("MYPROMOCODE", event.getId(), null, event.getBegin(), event.getEnd(), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "email@reference.ch", PromoCodeDiscount.CodeType.DISCOUNT, null); + eventManager.addPromoCode("MYPROMOCODE", event.getId(), null, event.getBegin(), event.getEnd(), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "email@reference.ch", PromoCodeDiscount.CodeType.DISCOUNT, null, null); //promo code at organization level - eventManager.addPromoCode("MYFIXEDPROMO", null, event.getOrganizationId(), event.getBegin(), event.getEnd(), 5, PromoCodeDiscount.DiscountType.FIXED_AMOUNT, null, null,"description", "email@reference.ch", PromoCodeDiscount.CodeType.DISCOUNT, null); + eventManager.addPromoCode("MYFIXEDPROMO", null, event.getOrganizationId(), event.getBegin(), event.getEnd(), 5, PromoCodeDiscount.DiscountType.FIXED_AMOUNT, null, null,"description", "email@reference.ch", PromoCodeDiscount.CodeType.DISCOUNT, null, "CHF"); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(3); + tr.setQuantity(3); tr.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -326,10 +328,10 @@ public void testTicketWithDiscount() { assertEquals("MYPROMOCODE", priceAndDiscount.getRight().get().getPromoCode()); // 3 * 10 chf is the normal price, 10% discount -> 300 discount - assertEquals(2700, totalPrice.getPriceWithVAT()); - assertEquals(27, totalPrice.getVAT()); - assertEquals(-300, totalPrice.getDiscount()); - assertEquals(1, totalPrice.getDiscountAppliedCount()); + assertEquals(2700, totalPrice.priceWithVAT()); + assertEquals(27, totalPrice.VAT()); + assertEquals(-300, totalPrice.discount()); + assertEquals(1, totalPrice.discountAppliedCount()); OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); assertEquals("27.00", orderSummary.getTotalPrice()); @@ -338,7 +340,7 @@ public void testTicketWithDiscount() { TicketReservationModification trFixed = new TicketReservationModification(); - trFixed.setAmount(3); + trFixed.setQuantity(3); trFixed.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification modFixed = new TicketReservationWithOptionalCodeModification(trFixed, Optional.empty()); @@ -350,10 +352,10 @@ public void testTicketWithDiscount() { assertEquals("MYFIXEDPROMO", priceAndDiscountFixed.getRight().get().getPromoCode()); // 3 * 10 chf is the normal price, 3 * 5 is the discount - assertEquals(2985, totalPriceFixed.getPriceWithVAT()); - assertEquals(30, totalPriceFixed.getVAT()); - assertEquals(-15, totalPriceFixed.getDiscount()); - assertEquals(3, totalPriceFixed.getDiscountAppliedCount()); + assertEquals(2985, totalPriceFixed.priceWithVAT()); + assertEquals(30, totalPriceFixed.VAT()); + assertEquals(-15, totalPriceFixed.discount()); + assertEquals(3, totalPriceFixed.discountAppliedCount()); OrderSummary orderSummaryFixed = ticketReservationManager.orderSummaryForReservationId(reservationIdFixed, event); assertEquals("29.85", orderSummaryFixed.getTotalPrice()); @@ -364,15 +366,15 @@ public void testTicketWithDiscount() { //check if we try to fetch more than the limit TicketReservationModification trTooMuch = new TicketReservationModification(); - trTooMuch.setAmount(4); + trTooMuch.setQuantity(4); trTooMuch.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification modTooMuch = new TicketReservationWithOptionalCodeModification(trTooMuch, Optional.empty()); - assertThrows(TicketReservationManager.TooManyTicketsForDiscountCodeException.class, + assertThrows(TooManyTicketsForDiscountCodeException.class, () -> ticketReservationManager.createTicketReservation(event, Collections.singletonList(modTooMuch ), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.of("MYPROMOCODE"), Locale.ENGLISH, false, null)); } @Test - public void testAdditionalServiceWithDiscount() { + void testAdditionalServiceWithDiscount() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -386,10 +388,10 @@ public void testAdditionalServiceWithDiscount() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new); //promo code at event level - eventManager.addPromoCode("MYPROMOCODE", event.getId(), null, event.getBegin(), event.getEnd(), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "email@reference.ch", PromoCodeDiscount.CodeType.DISCOUNT, null); + eventManager.addPromoCode("MYPROMOCODE", event.getId(), null, event.getBegin(), event.getEnd(), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "email@reference.ch", PromoCodeDiscount.CodeType.DISCOUNT, null, null); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(3); + tr.setQuantity(3); tr.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -412,12 +414,12 @@ public void testAdditionalServiceWithDiscount() { // totalPrice is (ticketPrice * 3) + (asPrice) + (donationPrice) - (discount * 4) - assertEquals(4100, totalPrice.getPriceWithVAT()); - assertEquals(-400, totalPrice.getDiscount()); + assertEquals(4100, totalPrice.priceWithVAT()); + assertEquals(-400, totalPrice.discount()); } @Test - public void testAccessCode() { + void testAccessCode() { testTicketsWithAccessCode(); } @@ -435,10 +437,10 @@ private Triple<Event, TicketCategory, String> testTicketsWithAccessCode() { //promo code at event level String accessCode = ACCESS_CODE; - eventManager.addPromoCode(accessCode, event.getId(), null, event.getBegin(), event.getEnd(), 0, null, null, 3, "description", "email@reference.ch", PromoCodeDiscount.CodeType.ACCESS, category.getId()); + eventManager.addPromoCode(accessCode, event.getId(), null, event.getBegin(), event.getEnd(), 0, null, null, 3, "description", "email@reference.ch", PromoCodeDiscount.CodeType.ACCESS, category.getId(), null); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(3); + tr.setQuantity(3); tr.setTicketCategoryId(category.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -451,10 +453,10 @@ private Triple<Event, TicketCategory, String> testTicketsWithAccessCode() { // 3 * 10 chf is the normal price, 10% discount -> 300 discount - assertEquals(3000, totalPrice.getPriceWithVAT()); - assertEquals(30, totalPrice.getVAT()); - assertEquals(0, totalPrice.getDiscount()); - assertEquals(0, totalPrice.getDiscountAppliedCount()); + assertEquals(3000, totalPrice.priceWithVAT()); + assertEquals(30, totalPrice.VAT()); + assertEquals(0, totalPrice.discount()); + assertEquals(0, totalPrice.discountAppliedCount()); OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); assertEquals("30.00", orderSummary.getTotalPrice()); @@ -466,21 +468,21 @@ private Triple<Event, TicketCategory, String> testTicketsWithAccessCode() { } @Test - public void testAccessCodeLimit() { + void testAccessCodeLimit() { var triple = testTicketsWithAccessCode(); TicketReservationModification trTooMuch = new TicketReservationModification(); - trTooMuch.setAmount(1); + trTooMuch.setQuantity(1); trTooMuch.setTicketCategoryId(triple.getMiddle().getId()); TicketReservationWithOptionalCodeModification modTooMuch = new TicketReservationWithOptionalCodeModification(trTooMuch, Optional.empty()); - assertThrows(TicketReservationManager.TooManyTicketsForDiscountCodeException.class, + assertThrows(TooManyTicketsForDiscountCodeException.class, () -> ticketReservationManager.createTicketReservation(triple.getLeft(), List.of(modTooMuch), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.of(ACCESS_CODE), Locale.ENGLISH, false, null)); } @Test - public void testAccessCodeReleaseTickets() { + void testAccessCodeReleaseTickets() { var triple = testTicketsWithAccessCode(); TicketReservationModification trTooMuch = new TicketReservationModification(); - trTooMuch.setAmount(1); + trTooMuch.setQuantity(1); trTooMuch.setTicketCategoryId(triple.getMiddle().getId()); TicketReservationWithOptionalCodeModification modTooMuch = new TicketReservationWithOptionalCodeModification(trTooMuch, Optional.empty()); ticketReservationManager.cancelPendingReservation(triple.getRight(), true, null); @@ -492,7 +494,7 @@ public void testAccessCodeReleaseTickets() { } @Test - public void testWithAdditionalServices() { + void testWithAdditionalServices() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -507,7 +509,7 @@ public void testWithAdditionalServices() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(3); + tr.setQuantity(3); tr.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -523,24 +525,24 @@ public void testWithAdditionalServices() { TotalPrice totalPrice = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - assertEquals(4000, totalPrice.getPriceWithVAT());//3 tickets + 1 AS - assertEquals(40, totalPrice.getVAT()); - assertEquals(0, totalPrice.getDiscount()); - assertEquals(0, totalPrice.getDiscountAppliedCount()); + assertEquals(4000, totalPrice.priceWithVAT());//3 tickets + 1 AS + assertEquals(40, totalPrice.VAT()); + assertEquals(0, totalPrice.discount()); + assertEquals(0, totalPrice.discountAppliedCount()); OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event); assertEquals("40.00", orderSummary.getTotalPrice()); assertEquals("0.40", orderSummary.getTotalVAT()); assertEquals(3, orderSummary.getTicketAmount()); - List<SummaryRow> asRows = orderSummary.getSummary().stream().filter(s -> s.getType() == SummaryRow.SummaryType.ADDITIONAL_SERVICE).collect(Collectors.toList()); + List<SummaryRow> asRows = orderSummary.getSummary().stream().filter(s -> s.type() == SummaryRow.SummaryType.ADDITIONAL_SERVICE).toList(); assertEquals(1, asRows.size()); - assertEquals("9.90", asRows.get(0).getPriceBeforeVat()); - assertEquals("9.90", asRows.get(0).getSubTotalBeforeVat()); - assertEquals("10.00", asRows.get(0).getSubTotal()); + assertEquals("9.90", asRows.get(0).priceBeforeVat()); + assertEquals("9.90", asRows.get(0).subTotalBeforeVat()); + assertEquals("10.00", asRows.get(0).subTotal()); } @Test - public void testTicketSelectionNotEnoughTicketsAvailable() { + void testTicketSelectionNotEnoughTicketsAvailable() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -551,15 +553,15 @@ public void testTicketSelectionNotEnoughTicketsAvailable() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(AVAILABLE_SEATS + 1); + tr.setQuantity(AVAILABLE_SEATS + 1); tr.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); - assertThrows(TicketReservationManager.NotEnoughTicketsException.class, () -> ticketReservationManager.createTicketReservation(event, Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null)); + assertThrows(NotEnoughTicketsException.class, () -> ticketReservationManager.createTicketReservation(event, Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null)); } @Test - public void testDeletePendingPaymentUnboundedCategory() { + void testDeletePendingPaymentUnboundedCategory() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -570,7 +572,7 @@ public void testDeletePendingPaymentUnboundedCategory() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(AVAILABLE_SEATS / 2 + 1); + tr.setQuantity(AVAILABLE_SEATS / 2 + 1); tr.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -578,12 +580,12 @@ public void testDeletePendingPaymentUnboundedCategory() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = ticketReservationManager.performPayment(specification, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); assertTrue(result.isSuccessful()); - ticketReservationManager.deleteOfflinePayment(event, reservationId, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationId, false, false, false, null); waitingQueueManager.distributeSeats(event); mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -591,7 +593,7 @@ public void testDeletePendingPaymentUnboundedCategory() { priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification2 = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification2 = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); result = ticketReservationManager.performPayment(specification2, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); @@ -600,7 +602,7 @@ public void testDeletePendingPaymentUnboundedCategory() { @Test - public void testCleanupExpiredReservations() { + void testCleanupExpiredReservations() { var testCases = List.of( // 1st test case: bounded category, max 10 tickets @@ -625,7 +627,7 @@ public void testCleanupExpiredReservations() { boolean bounded = category.isBounded(); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(10); + tr.setQuantity(10); tr.setTicketCategoryId(category.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -663,7 +665,7 @@ public void testCleanupExpiredReservations() { } @Test - public void testCleanupOfflineExpiredReservations() { + void testCleanupOfflineExpiredReservations() { List<TicketCategoryModification> categories = List.of( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -676,7 +678,7 @@ public void testCleanupOfflineExpiredReservations() { TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(10); + tr.setQuantity(10); tr.setTicketCategoryId(bounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -693,7 +695,7 @@ public void testCleanupOfflineExpiredReservations() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = ticketReservationManager.performPayment(specification, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); diff --git a/src/test/java/alfio/manager/TicketReservationManagerTest.java b/src/test/java/alfio/manager/TicketReservationManagerTest.java index 786f1feef5..23133b3cf5 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerTest.java @@ -21,10 +21,10 @@ import alfio.manager.PaymentManager.PaymentMethodDTO.PaymentMethodStatus; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.payment.*; -import alfio.manager.support.PartialTicketTextGenerator; -import alfio.manager.support.PaymentResult; -import alfio.manager.support.PaymentWebhookResult; -import alfio.manager.support.TemplateGenerator; +import alfio.manager.support.*; +import alfio.manager.support.reservation.OrderSummaryGenerator; +import alfio.manager.support.reservation.ReservationCostCalculator; +import alfio.manager.support.reservation.ReservationEmailContentHelper; import alfio.manager.system.ConfigurationManager; import alfio.manager.system.ConfigurationManager.MaybeConfiguration; import alfio.manager.testSupport.MaybeConfigurationBuilder; @@ -35,6 +35,7 @@ import alfio.model.modification.TicketReservationWithOptionalCodeModification; import alfio.model.system.ConfigurationKeyValuePathLevel; import alfio.model.system.ConfigurationKeys; +import alfio.model.system.command.FinalizeReservation; import alfio.model.transaction.PaymentContext; import alfio.model.transaction.PaymentMethod; import alfio.model.transaction.PaymentProxy; @@ -45,16 +46,19 @@ import alfio.model.user.Organization; import alfio.model.user.Role; import alfio.repository.*; +import alfio.repository.system.AdminJobQueueRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; import alfio.test.util.TestUtil; import alfio.util.*; import ch.digitalfondue.npjt.AffectedRowCountAndKey; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.MessageSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -139,6 +143,11 @@ BANK_ACCOUNT_NR, new MaybeConfiguration(BANK_ACCOUNT_NR), BANK_ACCOUNT_OWNER, new MaybeConfiguration(BANK_ACCOUNT_OWNER)); private PromoCodeDiscountRepository promoCodeDiscountRepository; private BillingDocumentManager billingDocumentManager; + private ApplicationEventPublisher applicationEventPublisher; + private ReservationEmailContentHelper reservationHelper; + private ReservationFinalizer reservationFinalizer; + private ReservationCostCalculator reservationCostCalculator; + private ReservationMetadata metadata; @BeforeEach void init() { @@ -210,6 +219,15 @@ void init() { when(purchaseContextManager.findByReservationId(anyString())).thenReturn(Optional.of(event)); billingDocumentManager = mock(BillingDocumentManager.class); + applicationEventPublisher = mock(ApplicationEventPublisher.class); + reservationHelper = mock(ReservationEmailContentHelper.class); + reservationCostCalculator = mock(ReservationCostCalculator.class); + var osm = mock(OrderSummaryGenerator.class); + reservationFinalizer = new ReservationFinalizer(transactionManager, + ticketReservationRepository, userRepository, extensionManager, auditingRepository, TestUtil.clockProvider(), + configurationManager, null, ticketRepository, reservationHelper, specialPriceRepository, + waitingQueueManager, ticketCategoryRepository, reservationCostCalculator, billingDocumentManager, additionalServiceItemRepository, + osm, transactionRepository, mock(AdminJobQueueRepository.class), purchaseContextManager, mock(Json.class)); trm = new TicketReservationManager(eventRepository, organizationRepository, ticketRepository, @@ -242,7 +260,12 @@ void init() { TestUtil.clockProvider(), purchaseContextManager, mock(SubscriptionRepository.class), - mock(UserManager.class)); + mock(UserManager.class), + applicationEventPublisher, + reservationCostCalculator, + reservationHelper, + reservationFinalizer, + osm); when(event.getId()).thenReturn(EVENT_ID); when(event.getOrganizationId()).thenReturn(ORGANIZATION_ID); @@ -277,6 +300,9 @@ void init() { when(messageSource.getMessage(eq("reminder.ticket-not-assigned.subject"), any(), any())).thenReturn("subject"); when(billingDocumentRepository.insert(anyInt(), anyString(), anyString(), any(), anyString(), any(), anyInt())).thenReturn(new AffectedRowCountAndKey<>(1, 1L)); totalPrice = mock(TotalPrice.class); + metadata = mock(ReservationMetadata.class); + when(ticketReservationRepository.getMetadata(RESERVATION_ID)).thenReturn(metadata); + when(metadata.isFinalized()).thenReturn(true); } private void initUpdateTicketOwner(Ticket original, Ticket modified, String ticketId, String originalEmail, String originalName, UpdateTicketOwnerForm form) { @@ -393,7 +419,7 @@ void fallbackToCurrentLocale() { when(original.getUserLanguage()).thenReturn(USER_LANGUAGE); trm.updateTicketOwner(original, Locale.ENGLISH, event, form, (a) -> null, ownerChangeTextBuilder, Optional.empty()); verify(messageSource, times(1)).getMessage(eq("ticket-has-changed-owner-subject"), any(), eq(Locale.ITALIAN)); - verify(notificationManager, times(1)).sendTicketByEmail(eq(modified), eq(event), eq(Locale.ENGLISH), any(), any(), any(), any()); + verify(reservationHelper).sendTicketByEmail(eq(modified), eq(Locale.ENGLISH), eq(event), any(PartialTicketTextGenerator.class)); verify(notificationManager, times(1)).sendSimpleEmail(eq(event), eq(RESERVATION_ID), eq(originalEmail), anyString(), any(TemplateGenerator.class)); } @@ -495,7 +521,7 @@ void doNotSendReminderTooEarly() { when(event.getBegin()).thenReturn(ZonedDateTime.now(ZoneId.of("UTC-8")).plusMonths(3).plusDays(1)); when(eventRepository.findAll()).thenReturn(singletonList(event)); when(ticketRepository.findAllReservationsConfirmedButNotAssignedForUpdate(anyInt())).thenReturn(singleton("abcd")); - List<Event> events = trm.getNotifiableEventsStream().collect(Collectors.toList()); + List<Event> events = trm.getNotifiableEventsStream().toList(); Assertions.assertEquals(0, events.size()); verify(notificationManager, never()).sendSimpleEmail(eq(event), anyString(), anyString(), anyString(), any(TemplateGenerator.class)); } @@ -786,7 +812,7 @@ void confirmPaidReservation() { when(configurationManager.getFor(eq(BANKING_KEY), any())).thenReturn(BANKING_INFO); mockBillingDocument(); testPaidReservation(true, true); - verify(notificationManager).sendTicketByEmail(any(), any(), any(), any(), any(), any(), any()); + verify(notificationManager, never()).sendTicketByEmail(any(), any(), any(), any(), any(), any(), any()); } @Test @@ -884,11 +910,15 @@ private void testPaidReservation(boolean enableTicketTransfer, boolean expectSuc var verificationMode = expectCompleteReservation ? times(1) : never(); verify(ticketReservationRepository, verificationMode).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.IN_PAYMENT.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), anyString(), any(), eq(PaymentProxy.STRIPE.toString()), isNull()); - verify(ticketRepository, verificationMode).updateTicketsStatusWithReservationId(eq(RESERVATION_ID), eq(TicketStatus.ACQUIRED.toString())); - verify(specialPriceRepository, verificationMode).updateStatusForReservation(eq(singletonList(RESERVATION_ID)), eq(SpecialPrice.Status.TAKEN.toString())); - verify(ticketReservationRepository, verificationMode).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.COMPLETE.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), anyString(), any(), eq(PaymentProxy.STRIPE.toString()), isNull()); - verify(waitingQueueManager, verificationMode).fireReservationConfirmed(eq(RESERVATION_ID)); - verify(billingDocumentManager, verificationMode).generateInvoiceNumber(eq(spec), any()); + verify(billingDocumentManager, never()).generateInvoiceNumber(eq(spec), any()); + verify(specialPriceRepository, verificationMode).updateStatusForReservation(singletonList(RESERVATION_ID), SpecialPrice.Status.TAKEN.toString()); + + if (expectCompleteReservation) { + verify(applicationEventPublisher).publishEvent(new FinalizeReservation(spec, PaymentProxy.STRIPE, true, true, null, PENDING)); + } + verify(ticketRepository, never()).updateTicketsStatusWithReservationId(RESERVATION_ID, TicketStatus.ACQUIRED.toString()); + verify(ticketReservationRepository, never()).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.COMPLETE.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), anyString(), any(), eq(PaymentProxy.STRIPE.toString()), isNull()); + verify(waitingQueueManager, never()).fireReservationConfirmed(RESERVATION_ID); verify(ticketReservationRepository, never()).setInvoiceNumber(eq(RESERVATION_ID), any()); } else { Assertions.assertFalse(result.isSuccessful()); @@ -910,16 +940,17 @@ void returnFailureCodeIfPaymentNotSuccessful() { when(paymentManager.streamActiveProvidersByProxy(eq(PaymentProxy.STRIPE), any())).thenReturn(Stream.of(stripeCreditCardManager)); when(stripeCreditCardManager.getTokenAndPay(any())).thenReturn(PaymentResult.failed("error-code")); when(stripeCreditCardManager.accept(eq(PaymentMethod.CREDIT_CARD), any(), any())).thenReturn(true); + when(ticketReservationRepository.findOptionalStatusAndValidationById(RESERVATION_ID)).thenReturn(Optional.of(new TicketReservationStatusAndValidation(PENDING, true))); PaymentSpecification spec = new PaymentSpecification(RESERVATION_ID, new StripeCreditCardToken(GATEWAY_TOKEN), 100, event, "email@user", new CustomerName("Full Name", null, null, event.mustUseFirstAndLastName()), null, null, Locale.ENGLISH, true, false, null, "IT", "12345", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = trm.performPayment(spec, new TotalPrice(100, 0, 0, 0, "CHF"), PaymentProxy.STRIPE, PaymentMethod.CREDIT_CARD, null); Assertions.assertFalse(result.isSuccessful()); Assertions.assertFalse(result.getGatewayId().isPresent()); Assertions.assertEquals(Optional.of("error-code"), result.getErrorCode()); verify(ticketReservationRepository).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.IN_PAYMENT.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), isNull(), isNull(), eq(PaymentProxy.STRIPE.toString()), isNull()); - verify(ticketReservationRepository).findReservationByIdForUpdate(eq(RESERVATION_ID)); - verify(ticketReservationRepository).updateReservationStatus(eq(RESERVATION_ID), eq(TicketReservationStatus.PENDING.toString())); - verify(configurationManager, never()).hasAllConfigurationsForInvoice(eq(event)); - verify(ticketReservationRepository).updateBillingData(eq(PriceContainer.VatStatus.INCLUDED), eq(100), eq(100), eq(0), eq(0), eq(EVENT_CURRENCY), eq("12345"), eq("IT"), eq(true), eq(RESERVATION_ID)); + verify(ticketReservationRepository).findReservationByIdForUpdate(RESERVATION_ID); + verify(ticketReservationRepository).updateReservationStatus(RESERVATION_ID, TicketReservationStatus.PENDING.toString()); + verify(configurationManager, never()).hasAllConfigurationsForInvoice(event); + verify(ticketReservationRepository).updateBillingData(PriceContainer.VatStatus.INCLUDED, 100, 100, 0, 0, EVENT_CURRENCY, "12345", "IT", true, RESERVATION_ID); } @Test @@ -945,13 +976,15 @@ void handleOnSitePaymentMethod() { PaymentResult result = trm.performPayment(spec, new TotalPrice(100, 0, 0, 0, "CHF"), PaymentProxy.ON_SITE, PaymentMethod.ON_SITE, null); Assertions.assertTrue(result.isSuccessful()); Assertions.assertEquals(Optional.of(TicketReservationManager.NOT_YET_PAID_TRANSACTION_ID), result.getGatewayId()); - verify(ticketReservationRepository).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.COMPLETE.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), anyString(), any(), eq(PaymentProxy.ON_SITE.toString()), isNull()); + verify(specialPriceRepository).updateStatusForReservation(singletonList(RESERVATION_ID), SpecialPrice.Status.TAKEN.toString()); + verify(applicationEventPublisher).publishEvent(new FinalizeReservation(spec, PaymentProxy.ON_SITE, true, true, null, PENDING)); + verify(ticketRepository, never()).updateTicketsStatusWithReservationId(RESERVATION_ID, TicketStatus.ACQUIRED.toString()); + verify(ticketReservationRepository, never()).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.COMPLETE.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), anyString(), any(), eq(PaymentProxy.ON_SITE.toString()), isNull()); + verify(waitingQueueManager, never()).fireReservationConfirmed(RESERVATION_ID); + verify(ticketReservationRepository, never()).setInvoiceNumber(eq(RESERVATION_ID), any()); verify(ticketReservationRepository).findReservationByIdForUpdate(eq(RESERVATION_ID)); - verify(ticketRepository).updateTicketsStatusWithReservationId(eq(RESERVATION_ID), eq(TicketStatus.TO_BE_PAID.toString())); - verify(specialPriceRepository).updateStatusForReservation(eq(singletonList(RESERVATION_ID)), eq(SpecialPrice.Status.TAKEN.toString())); - verify(waitingQueueManager).fireReservationConfirmed(eq(RESERVATION_ID)); verify(ticketReservationRepository, atLeastOnce()).findReservationById(RESERVATION_ID); - verify(billingDocumentManager).generateInvoiceNumber(eq(spec), any()); + verify(billingDocumentManager, never()).generateInvoiceNumber(eq(spec), any()); verify(ticketReservationRepository).updateBillingData(eq(PriceContainer.VatStatus.INCLUDED), eq(100), eq(100), eq(0), eq(0), eq(EVENT_CURRENCY), eq("123456"), eq("IT"), eq(true), eq(RESERVATION_ID)); verify(ticketRepository, atLeastOnce()).findTicketsInReservation(anyString()); } @@ -977,8 +1010,8 @@ void handleOfflinePaymentMethod() { Assertions.assertEquals(Optional.of(TicketReservationManager.NOT_YET_PAID_TRANSACTION_ID), result.getGatewayId()); verify(waitingQueueManager, never()).fireReservationConfirmed(eq(RESERVATION_ID)); verify(ticketReservationRepository).findReservationByIdForUpdate(eq(RESERVATION_ID)); - verify(billingDocumentManager).generateInvoiceNumber(eq(spec), any()); - verify(ticketReservationRepository).setInvoiceNumber(RESERVATION_ID, invoiceNumber); + verify(billingDocumentManager, never()).generateInvoiceNumber(eq(spec), any()); + verify(ticketReservationRepository, never()).setInvoiceNumber(RESERVATION_ID, invoiceNumber); verify(ticketReservationRepository).updateBillingData(eq(PriceContainer.VatStatus.INCLUDED), eq(100), eq(100), eq(0), eq(0), eq(EVENT_CURRENCY), eq("123456"), eq("IT"), eq(true), eq(RESERVATION_ID)); } @@ -1023,18 +1056,20 @@ void confirmOfflinePayments() { when(json.fromJsonString(anyString(), eq(OrderSummary.class))).thenReturn(mock(OrderSummary.class)); when(json.asJsonString(any())).thenReturn("{}"); when(configurationManager.getFor(eq(EnumSet.of(DEFERRED_BANK_TRANSFER_ENABLED, DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL)), any())).thenReturn(Map.of(DEFERRED_BANK_TRANSFER_ENABLED, new MaybeConfiguration(DEFERRED_BANK_TRANSFER_ENABLED))); - - trm.confirmOfflinePayment(event, RESERVATION_ID, "username"); + when(reservationCostCalculator.totalReservationCostWithVAT(RESERVATION_ID)).thenReturn(Pair.of(new TotalPrice(0, 0, 0, 0, "CHF"), Optional.empty())); + assertThrows(IncompatibleStateException.class, () -> trm.confirmOfflinePayment(event, RESERVATION_ID, null, "username")); + when(metadata.isReadyForConfirmation()).thenReturn(true); + trm.confirmOfflinePayment(event, RESERVATION_ID, null, "username"); verify(ticketReservationRepository, atLeastOnce()).findOptionalReservationById(RESERVATION_ID); verify(ticketReservationRepository, atLeastOnce()).findReservationById(RESERVATION_ID); - verify(ticketReservationRepository).lockReservationForUpdate(eq(RESERVATION_ID)); + verify(ticketReservationRepository, times(2)).lockReservationForUpdate(eq(RESERVATION_ID)); verify(ticketReservationRepository).confirmOfflinePayment(eq(RESERVATION_ID), eq(COMPLETE.toString()), any(ZonedDateTime.class)); verify(ticketRepository).updateTicketsStatusWithReservationId(eq(RESERVATION_ID), eq(TicketStatus.ACQUIRED.toString())); verify(ticketReservationRepository).updateTicketReservation(eq(RESERVATION_ID), eq(TicketReservationStatus.COMPLETE.toString()), anyString(), anyString(), isNull(), isNull(), anyString(), isNull(), any(), eq(PaymentProxy.OFFLINE.toString()), isNull()); verify(waitingQueueManager).fireReservationConfirmed(eq(RESERVATION_ID)); verify(ticketRepository, atLeastOnce()).findTicketsInReservation(RESERVATION_ID); verify(specialPriceRepository).updateStatusForReservation(eq(singletonList(RESERVATION_ID)), eq(SpecialPrice.Status.TAKEN.toString())); - verify(configurationManager, atLeastOnce()).getShortReservationID(eq(event), any(TicketReservation.class)); + verify(reservationHelper).sendConfirmationEmail(event, copy, Locale.ENGLISH, "username"); verify(ticketRepository).countTicketsInReservation(eq(RESERVATION_ID)); verify(configurationManager).getFor(eq(PLATFORM_MODE_ENABLED), any()); } @@ -1115,6 +1150,7 @@ void reservationURLGeneration() { when(ticketReservation.getId()).thenReturn(RESERVATION_ID); when(ticketReservationRepository.findReservationById(RESERVATION_ID)).thenReturn(ticketReservation); when(ticketRepository.findByUUID(ticketId)).thenReturn(ticket); + when(ticket.getUuid()).thenReturn(ticketId); when(ticket.getUserLanguage()).thenReturn(USER_LANGUAGE); //generate the reservationUrl from RESERVATION_ID Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en", trm.reservationUrl(RESERVATION_ID)); @@ -1128,7 +1164,7 @@ void reservationURLGeneration() { //generate the ticket URL Assertions.assertEquals(BASE_URL + "event/" + shortName + "/ticket/ticketId?lang=it", trm.ticketUrl(event, ticketId)); //generate the ticket update URL - Assertions.assertEquals(BASE_URL + "event/" + shortName + "/ticket/ticketId/update?lang=it", trm.ticketUpdateUrl(event, "ticketId")); + Assertions.assertEquals(BASE_URL + "event/" + shortName + "/ticket/ticketId/update?lang=it", ReservationUtil.ticketUpdateUrl(event, ticket, configurationManager)); } @Test @@ -1148,13 +1184,16 @@ void reservationUrlForExternalClients() { // OpenID active when(maybeOpenId.getValueAsBooleanOrDefault()).thenReturn(true); - Assertions.assertEquals(BASE_URL + "openid/authentication?reservation=" + RESERVATION_ID + "&contextType=" + PurchaseContext.PurchaseContextType.event + "&id=" + shortName, trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", true)); + Assertions.assertEquals(BASE_URL + "openid/authentication?reservation=" + RESERVATION_ID + "&contextType=" + PurchaseContext.PurchaseContextType.event + "&id=" + shortName, trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", true, null)); // user not specified in the request - Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en", trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", false)); + Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en", trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", false, null)); // OpenID not active when(maybeOpenId.getValueAsBooleanOrDefault()).thenReturn(false); - Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en", trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", true)); + Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en", trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", true, null)); + // SubscriptionId is present + var subscriptionId = "subscription-id"; + Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en&subscription=" + subscriptionId, trm.reservationUrlForExternalClients(RESERVATION_ID, event, "en", true, subscriptionId)); } //sendReminderForOptionalInfo @@ -1345,7 +1384,7 @@ class TestCancelReservationsPendingPayment { private static final String PAYMENT_ID = "paymentId"; private final List<String> reservationIds = List.of(EXPIRED_RESERVATION_ID, PENDING_RESERVATION_ID); private final List<String> expiredReservationIds = List.of(EXPIRED_RESERVATION_ID); - Date now = new Date(Instant.now(ClockProvider.clock()).getEpochSecond()); + Date now = new Date(Instant.now(TestUtil.clockProvider().getClock()).getEpochSecond()); Transaction transactionMock; TicketReservation pendingReservationMock; PurchaseContext purchaseContextMock; @@ -1380,6 +1419,7 @@ void cancelExpiredReservationsPendingPaymentConfirmed() { .thenReturn(Optional.of(stripeManager)); when(stripeManager.forceTransactionCheck(eq(pendingReservationMock), eq(transactionMock), any())) .thenReturn(PaymentWebhookResult.successful(new StripeCreditCardToken(""))); + when(reservationCostCalculator.totalReservationCostWithVAT(pendingReservationMock)).thenReturn(Pair.of(new TotalPrice(0, 0, 0, 0, "CHF"), Optional.empty())); trm.cleanupExpiredReservations(now); verify(ticketReservationRepository).findExpiredReservationForUpdate(now); verify(specialPriceRepository).resetToFreeAndCleanupForReservation(expiredReservationIds); @@ -1390,7 +1430,6 @@ void cancelExpiredReservationsPendingPaymentConfirmed() { verify(ticketReservationRepository).getReservationIdAndEventId(expiredReservationIds); verify(ticketReservationRepository).findReservationsWithPendingTransaction(reservationIds); verify(ticketReservationRepository).findOptionalStatusAndValidationById(PENDING_RESERVATION_ID); - verify(ticketRepository, atLeastOnce()).findTicketsInReservation(PENDING_RESERVATION_ID); verifyNoMoreInteractions(ticketReservationRepository, specialPriceRepository, ticketRepository); } @@ -1435,10 +1474,16 @@ class TestSendReservationEmailIfNecessary { private MaybeConfiguration sendReservationEmailIfNecessary; private MaybeConfiguration sendTickets; private final String reservationEmail = "blabla@example.org"; + private ReservationFinalizer finalizer; @BeforeEach @SuppressWarnings("unchecked") void setUp() { + finalizer = new ReservationFinalizer(mock(PlatformTransactionManager.class), + ticketReservationRepository, userRepository, mock(ExtensionManager.class), auditingRepository, mock(ClockProvider.class), configurationManager, + mock(SubscriptionRepository.class), ticketRepository, reservationHelper, mock(SpecialPriceRepository.class), + waitingQueueManager, ticketCategoryRepository, mock(ReservationCostCalculator.class), billingDocumentManager, mock(AdditionalServiceItemRepository.class), + mock(OrderSummaryGenerator.class), transactionRepository, mock(AdminJobQueueRepository.class), purchaseContextManager, mock(Json.class)); sendReservationEmailIfNecessary = mock(MaybeConfiguration.class); sendTickets = mock(MaybeConfiguration.class); when(ticketReservation.getSrcPriceCts()).thenReturn(0); @@ -1454,56 +1499,65 @@ void setUp() { @Test void emailSentBecauseReservationIsNotFreeOfCharge() { when(ticketReservation.getSrcPriceCts()).thenReturn(1); - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), eq(reservationEmail), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailSentBecauseThereIsMoreThanOneTicketInTheReservation() { - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket, ticket), event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), eq(reservationEmail), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket, ticket), event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailSentBecauseTicketListIsNull() { - trm.sendConfirmationEmailIfNecessary(ticketReservation, null, event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), anyString(), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, null, event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailSentBecauseTicketListIsEmpty() { - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(), event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), anyString(), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(), event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailSentBecauseTicketHolderEmailIsDifferentFromReservation() { when(ticket.getEmail()).thenReturn("blabla2@example.org"); - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), eq(reservationEmail), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailSentBecauseFlagIsSetToFalse() { when(sendReservationEmailIfNecessary.getValueAsBooleanOrDefault()).thenReturn(false); - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), eq(reservationEmail), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailSentBecauseTicketIsNotSent() { when(sendReservationEmailIfNecessary.getValueAsBooleanOrDefault()).thenReturn(true); when(sendTickets.getValueAsBooleanOrDefault()).thenReturn(false); - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); - verify(notificationManager).sendSimpleEmail(eq(event), anyString(), eq(reservationEmail), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); + verify(reservationHelper).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } @Test void emailNOTSentBecauseFlagIsSetToTrue() { when(sendReservationEmailIfNecessary.getValueAsBooleanOrDefault()).thenReturn(true); when(sendTickets.getValueAsBooleanOrDefault()).thenReturn(true); - trm.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); - verify(notificationManager, never()).sendSimpleEmail(eq(event), anyString(), anyString(), isNull(), any(), anyList()); + finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null); + verify(reservationHelper, never()).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); + } + + @Test + void emailNotSentBecauseReservationNotFinalized() { + when(metadata.isFinalized()).thenReturn(false); + when(sendReservationEmailIfNecessary.getValueAsBooleanOrDefault()).thenReturn(true); + when(sendTickets.getValueAsBooleanOrDefault()).thenReturn(true); + assertThrows(IncompatibleStateException.class, () -> finalizer.sendConfirmationEmailIfNecessary(ticketReservation, List.of(ticket), event, Locale.ENGLISH, null)); + verify(reservationHelper, never()).sendConfirmationEmail(event, ticketReservation, Locale.ENGLISH, null); } } } \ No newline at end of file diff --git a/src/test/java/alfio/manager/TicketReservationManagerUnitTest.java b/src/test/java/alfio/manager/TicketReservationManagerUnitTest.java index c559bed1cb..5ae72e942a 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerUnitTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerUnitTest.java @@ -17,6 +17,9 @@ package alfio.manager; import alfio.manager.i18n.MessageSourceManager; +import alfio.manager.support.reservation.OrderSummaryGenerator; +import alfio.manager.support.reservation.ReservationCostCalculator; +import alfio.manager.support.reservation.ReservationEmailContentHelper; import alfio.manager.system.ConfigurationManager; import alfio.manager.user.UserManager; import alfio.model.*; @@ -27,10 +30,11 @@ import alfio.test.util.TestUtil; import alfio.util.Json; import alfio.util.TemplateManager; -import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.MessageSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; @@ -80,6 +84,7 @@ class TicketReservationManagerUnitTest { private ExtensionManager extensionManager; private GroupManager groupManager; private Json json; + private ReservationCostCalculator reservationCostCalculator; @BeforeEach public void setUp() { @@ -125,7 +130,7 @@ public void setUp() { when(messageSourceManager.getRootMessageSource()).thenReturn(messageSource); var purchaseContextManager = mock(PurchaseContextManager.class); when(purchaseContextManager.findByReservationId(anyString())).thenReturn(Optional.of(event)); - + reservationCostCalculator = mock(ReservationCostCalculator.class); manager = new TicketReservationManager(eventRepository, organizationRepository, ticketRepository, @@ -158,148 +163,106 @@ public void setUp() { TestUtil.clockProvider(), purchaseContextManager, mock(SubscriptionRepository.class), - mock(UserManager.class)); - - } - - @Test - void calcReservationCostOnlyTickets() { - when(event.isVatIncluded()).thenReturn(true, false); - when(event.getVat()).thenReturn(BigDecimal.TEN); - when(eventRepository.findByReservationId(eq(TICKET_RESERVATION_ID))).thenReturn(event); - when(ticketReservationRepository.findReservationById(eq(TICKET_RESERVATION_ID))).thenReturn(reservation); - when(reservation.getId()).thenReturn(TICKET_RESERVATION_ID); - when(ticket.getSrcPriceCts()).thenReturn(10); - when(ticketRepository.findTicketsInReservation(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.singletonList(ticket)); - AdditionalServiceItemRepository additionalServiceItemRepository = mock(AdditionalServiceItemRepository.class); - when(additionalServiceItemRepository.findByReservationUuid(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.emptyList()); - - when(event.getVatStatus()).thenReturn(PriceContainer.VatStatus.INCLUDED); - Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); - TotalPrice included = priceAndDiscount.getLeft(); - Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); - Assertions.assertEquals(10, included.getPriceWithVAT()); - Assertions.assertEquals(1, included.getVAT()); - - when(event.getVatStatus()).thenReturn(PriceContainer.VatStatus.NOT_INCLUDED); - Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscountNotIncluded = manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); - TotalPrice notIncluded = priceAndDiscountNotIncluded.getLeft(); - Assertions.assertTrue(priceAndDiscountNotIncluded.getRight().isEmpty()); - Assertions.assertEquals(11, notIncluded.getPriceWithVAT()); - Assertions.assertEquals(1, notIncluded.getVAT()); - } - - @Test - void calcReservationCostWithASVatIncludedInherited() { - initReservationWithAdditionalServices(true, AdditionalService.VatType.INHERITED, 10, 10); - //first: event price vat included, additional service VAT inherited - Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); - TotalPrice first = priceAndDiscount.getLeft(); - Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); - Assertions.assertEquals(20, first.getPriceWithVAT()); - Assertions.assertEquals(2, first.getVAT()); - } - - @Test - void calcReservationCostWithASVatIncludedASNoVat() { - initReservationWithAdditionalServices(true, AdditionalService.VatType.NONE, 10, 10); - //second: event price vat included, additional service VAT n/a - Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); - TotalPrice second = priceAndDiscount.getLeft(); - Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); - Assertions.assertEquals(20, second.getPriceWithVAT()); - Assertions.assertEquals(1, second.getVAT()); - } - - @Test - void calcReservationCostWithASVatNotIncludedASInherited() { - initReservationWithAdditionalServices(false, AdditionalService.VatType.INHERITED, 10, 10); - //third: event price vat not included, additional service VAT inherited - Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); - TotalPrice third = priceAndDiscount.getLeft(); - Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); - Assertions.assertEquals(22, third.getPriceWithVAT()); - Assertions.assertEquals(2, third.getVAT()); - } - - @Test - void calcReservationCostWithASVatNotIncludedASNone() { - initReservationWithAdditionalServices(false, AdditionalService.VatType.NONE, 10, 10); - //fourth: event price vat not included, additional service VAT n/a - Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); - TotalPrice fourth = priceAndDiscount.getLeft(); - Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); - Assertions.assertEquals(21, fourth.getPriceWithVAT()); - Assertions.assertEquals(1, fourth.getVAT()); - } - - @Test - void testExtractSummaryVatNotIncluded() { - initReservationWithTicket(1000, false); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(1100, 100, 0, 0, "CHF")); - Assertions.assertEquals(1, summaryRows.size()); - Assertions.assertEquals("10.00", summaryRows.get(0).getPrice()); - } - - @Test - void testExtractSummaryVatIncluded() { - initReservationWithTicket(1000, true); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); - Assertions.assertEquals(1, summaryRows.size()); - Assertions.assertEquals("10.00", summaryRows.get(0).getPrice()); + mock(UserManager.class), + mock(ApplicationEventPublisher.class), + reservationCostCalculator, + mock(ReservationEmailContentHelper.class), + mock(ReservationFinalizer.class), + mock(OrderSummaryGenerator.class)); } @Test - void testExtractSummaryVatIncludedExempt() { - initReservationWithTicket(1000, true); - when(ticket.getVatStatus()).thenReturn(PriceContainer.VatStatus.INCLUDED_EXEMPT); - when(ticket.getFinalPriceCts()).thenReturn(909); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, PriceContainer.VatStatus.INCLUDED_EXEMPT, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); - Assertions.assertEquals(1, summaryRows.size()); - Assertions.assertEquals("9.09", summaryRows.get(0).getPrice()); + void totalReservationCostByID() { + manager.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + verify(reservationCostCalculator).totalReservationCostWithVAT(TICKET_RESERVATION_ID); } @Test - void testExtractSummaryVatNotIncludedExempt() { - initReservationWithTicket(1000, false); - when(ticket.getVatStatus()).thenReturn(PriceContainer.VatStatus.NOT_INCLUDED_EXEMPT); - when(ticket.getFinalPriceCts()).thenReturn(1000); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, PriceContainer.VatStatus.NOT_INCLUDED_EXEMPT, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); - Assertions.assertEquals(1, summaryRows.size()); - Assertions.assertEquals("10.00", summaryRows.get(0).getPrice()); + void totalReservationCost() { + manager.totalReservationCostWithVAT(reservation); + verify(reservationCostCalculator).totalReservationCostWithVAT(reservation); } - @Test - void testExtractSummaryVatNotIncludedASInherited() { - initReservationWithAdditionalServices(false, AdditionalService.VatType.INHERITED, 1000, 1000); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(2200, 200, 0, 0, "CHF")); - Assertions.assertEquals(2, summaryRows.size()); - summaryRows.forEach(r -> Assertions.assertEquals("10.00", r.getPrice(), String.format("%s failed", r.getType()))); - } - - @Test - void testExtractSummaryVatIncludedASInherited() { - initReservationWithAdditionalServices(true, AdditionalService.VatType.INHERITED, 1000, 1000); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(2000, 182, 0, 0, "CHF")); - Assertions.assertEquals(2, summaryRows.size()); - summaryRows.forEach(r -> Assertions.assertEquals("10.00", r.getPrice(), String.format("%s failed", r.getType()))); - } - - @Test - void testExtractSummaryVatNotIncludedASNone() { - initReservationWithAdditionalServices(false, AdditionalService.VatType.NONE, 1000, 1000); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); - Assertions.assertEquals(2, summaryRows.size()); - summaryRows.forEach(r -> Assertions.assertEquals("10.00", r.getPrice(), String.format("%s failed", r.getType()))); - } - - @Test - void testExtractSummaryVatIncludedASNone() { - initReservationWithAdditionalServices(true, AdditionalService.VatType.NONE, 1000, 1000); - List<SummaryRow> summaryRows = manager.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(2000, 100, 0, 0, "CHF")); - Assertions.assertEquals(2, summaryRows.size()); - Assertions.assertEquals("10.00", summaryRows.get(0).getPrice()); - Assertions.assertEquals("10.00", summaryRows.get(1).getPrice()); + @Nested + class GenerateSummary { + + private OrderSummaryGenerator generator; + + @BeforeEach + void setUp() { + var subscriptionRepository = mock(SubscriptionRepository.class); + generator = new OrderSummaryGenerator(ticketReservationRepository, auditingRepository, paymentManager, ticketCategoryRepository, additionalServiceTextRepository, subscriptionRepository, ticketRepository, messageSourceManager, + new ReservationCostCalculator(ticketReservationRepository, mock(PurchaseContextManager.class), promoCodeDiscountRepository, subscriptionRepository, ticketRepository, additionalServiceRepository, additionalServiceItemRepository)); + } + + @Test + void testExtractSummaryVatNotIncluded() { + initReservationWithTicket(1000, false); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(1100, 100, 0, 0, "CHF")); + Assertions.assertEquals(1, summaryRows.size()); + Assertions.assertEquals("10.00", summaryRows.get(0).price()); + } + + @Test + void testExtractSummaryVatIncluded() { + initReservationWithTicket(1000, true); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); + Assertions.assertEquals(1, summaryRows.size()); + Assertions.assertEquals("10.00", summaryRows.get(0).price()); + } + + @Test + void testExtractSummaryVatIncludedExempt() { + initReservationWithTicket(1000, true); + when(ticket.getVatStatus()).thenReturn(PriceContainer.VatStatus.INCLUDED_EXEMPT); + when(ticket.getFinalPriceCts()).thenReturn(909); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, PriceContainer.VatStatus.INCLUDED_EXEMPT, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); + Assertions.assertEquals(1, summaryRows.size()); + Assertions.assertEquals("9.09", summaryRows.get(0).price()); + } + + @Test + void testExtractSummaryVatNotIncludedExempt() { + initReservationWithTicket(1000, false); + when(ticket.getVatStatus()).thenReturn(PriceContainer.VatStatus.NOT_INCLUDED_EXEMPT); + when(ticket.getFinalPriceCts()).thenReturn(1000); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, PriceContainer.VatStatus.NOT_INCLUDED_EXEMPT, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); + Assertions.assertEquals(1, summaryRows.size()); + Assertions.assertEquals("10.00", summaryRows.get(0).price()); + } + + @Test + void testExtractSummaryVatNotIncludedASInherited() { + initReservationWithAdditionalServices(false, AdditionalService.VatType.INHERITED, 1000, 1000); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(2200, 200, 0, 0, "CHF")); + Assertions.assertEquals(2, summaryRows.size()); + summaryRows.forEach(r -> Assertions.assertEquals("10.00", r.price(), String.format("%s failed", r.type()))); + } + + @Test + void testExtractSummaryVatIncludedASInherited() { + initReservationWithAdditionalServices(true, AdditionalService.VatType.INHERITED, 1000, 1000); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(2000, 182, 0, 0, "CHF")); + Assertions.assertEquals(2, summaryRows.size()); + summaryRows.forEach(r -> Assertions.assertEquals("10.00", r.price(), String.format("%s failed", r.type()))); + } + + @Test + void testExtractSummaryVatNotIncludedASNone() { + initReservationWithAdditionalServices(false, AdditionalService.VatType.NONE, 1000, 1000); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(1000, 100, 0, 0, "CHF")); + Assertions.assertEquals(2, summaryRows.size()); + summaryRows.forEach(r -> Assertions.assertEquals("10.00", r.price(), String.format("%s failed", r.type()))); + } + + @Test + void testExtractSummaryVatIncludedASNone() { + initReservationWithAdditionalServices(true, AdditionalService.VatType.NONE, 1000, 1000); + List<SummaryRow> summaryRows = generator.extractSummary(TICKET_RESERVATION_ID, null, event, Locale.ENGLISH, null, new TotalPrice(2000, 100, 0, 0, "CHF")); + Assertions.assertEquals(2, summaryRows.size()); + Assertions.assertEquals("10.00", summaryRows.get(0).price()); + Assertions.assertEquals("10.00", summaryRows.get(1).price()); + } } private void initReservationWithTicket(int ticketPaidPrice, boolean eventVatIncluded) { @@ -336,8 +299,8 @@ private void initReservationWithAdditionalServices(boolean eventVatIncluded, Add AdditionalServiceItemRepository additionalServiceItemRepository = mock(AdditionalServiceItemRepository.class); when(additionalServiceItemRepository.findByReservationUuid(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.emptyList()); AdditionalServiceText text = mock(AdditionalServiceText.class); - when(text.getId()).thenReturn(1); - when(text.getLocale()).thenReturn("en"); + when(text.id()).thenReturn(1); + when(text.locale()).thenReturn("en"); when(additionalServiceTextRepository.findBestMatchByLocaleAndType(anyInt(), eq("en"), eq(AdditionalServiceText.TextType.TITLE))).thenReturn(text); } diff --git a/src/test/java/alfio/manager/UploadedResourceIntegrationTest.java b/src/test/java/alfio/manager/UploadedResourceIntegrationTest.java index 32571db5e3..a160288d71 100644 --- a/src/test/java/alfio/manager/UploadedResourceIntegrationTest.java +++ b/src/test/java/alfio/manager/UploadedResourceIntegrationTest.java @@ -29,6 +29,7 @@ import alfio.repository.EventRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -51,11 +52,10 @@ import static alfio.test.util.IntegrationTestUtil.initEvent; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class UploadedResourceIntegrationTest extends BaseIntegrationTest { +class UploadedResourceIntegrationTest extends BaseIntegrationTest { private static final byte[] FILE = {1,2,3,4}; private static final byte[] ONE_PIXEL_BLACK_GIF = Base64.getDecoder().decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="); diff --git a/src/test/java/alfio/manager/WaitingQueueManagerIntegrationTest.java b/src/test/java/alfio/manager/WaitingQueueManagerIntegrationTest.java index 2eeebf9fa5..e715860083 100644 --- a/src/test/java/alfio/manager/WaitingQueueManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/WaitingQueueManagerIntegrationTest.java @@ -36,6 +36,7 @@ import alfio.repository.TicketRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import alfio.util.MonetaryUtil; @@ -60,11 +61,10 @@ import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, WebSecurityConfig.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class WaitingQueueManagerIntegrationTest extends BaseIntegrationTest { +class WaitingQueueManagerIntegrationTest extends BaseIntegrationTest { private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); @@ -94,7 +94,7 @@ public class WaitingQueueManagerIntegrationTest extends BaseIntegrationTest { private ClockProvider clockProvider; @BeforeEach - public void init() { + void init() { ensureMinimalConfiguration(configurationRepository); } @@ -103,7 +103,7 @@ private static CustomerName customerJohnDoe(Event event) { } @Test - public void testSubscribeDenied() { + void testSubscribeDenied() { List<TicketCategoryModification> categories = getPreSalesTicketCategoryModifications(false, AVAILABLE_SEATS, true, 10); Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); Event event = pair.getKey(); @@ -118,7 +118,7 @@ public void testSubscribeDenied() { } @Test - public void testDistributeSeatsFirstCategoryIsUnbounded() { + void testDistributeSeatsFirstCategoryIsUnbounded() { List<TicketCategoryModification> categories = getPreSalesTicketCategoryModifications(false, AVAILABLE_SEATS, true, 10); Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); Event event = pair.getKey(); @@ -128,19 +128,19 @@ public void testDistributeSeatsFirstCategoryIsUnbounded() { configurationManager.saveSystemConfiguration(ConfigurationKeys.ENABLE_WAITING_QUEUE, "true"); boolean result = waitingQueueManager.subscribe(event, customerJohnDoe(event), "john@doe.com", null, Locale.ENGLISH); assertTrue(result); - List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).collect(Collectors.toList()); + List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).toList(); assertEquals(1, subscriptions.size()); Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime> subscriptionDetail = subscriptions.get(0); assertEquals("john@doe.com", subscriptionDetail.getLeft().getEmailAddress()); TicketReservationWithOptionalCodeModification reservation = subscriptionDetail.getMiddle(); assertEquals(Integer.valueOf(firstCategory.getId()), reservation.getTicketCategoryId()); - assertEquals(Integer.valueOf(1), reservation.getAmount()); + assertEquals(Integer.valueOf(1), reservation.getQuantity()); assertTrue(subscriptionDetail.getRight().isAfter(ZonedDateTime.now(clockProvider.getClock()))); } @Test - public void testDistributeSeatsFirstCategoryIsBounded() { + void testDistributeSeatsFirstCategoryIsBounded() { List<TicketCategoryModification> categories = getPreSalesTicketCategoryModifications(true, 10, true, 10); Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository); Event event = pair.getKey(); @@ -150,19 +150,19 @@ public void testDistributeSeatsFirstCategoryIsBounded() { configurationManager.saveSystemConfiguration(ConfigurationKeys.ENABLE_WAITING_QUEUE, "true"); boolean result = waitingQueueManager.subscribe(event, customerJohnDoe(event), "john@doe.com", null, Locale.ENGLISH); assertTrue(result); - List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).collect(Collectors.toList()); + List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).toList(); assertEquals(1, subscriptions.size()); Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime> subscriptionDetail = subscriptions.get(0); assertEquals("john@doe.com", subscriptionDetail.getLeft().getEmailAddress()); TicketReservationWithOptionalCodeModification reservation = subscriptionDetail.getMiddle(); assertEquals(Integer.valueOf(firstCategory.getId()), reservation.getTicketCategoryId()); - assertEquals(Integer.valueOf(1), reservation.getAmount()); + assertEquals(Integer.valueOf(1), reservation.getQuantity()); assertTrue(subscriptionDetail.getRight().isAfter(ZonedDateTime.now(clockProvider.getClock()))); } @Test - public void testWaitingQueueForUnboundedCategory() { + void testWaitingQueueForUnboundedCategory() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), @@ -172,7 +172,7 @@ public void testWaitingQueueForUnboundedCategory() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(AVAILABLE_SEATS); + tr.setQuantity(AVAILABLE_SEATS); tr.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -182,14 +182,14 @@ public void testWaitingQueueForUnboundedCategory() { TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); var orderSummary = ticketReservationManager.orderSummaryForReservation(reservation, event); - PaymentSpecification spec = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), event, "blabla", new CustomerName("a", "b", "c", true), "", null, Locale.ENGLISH, false, false, orderSummary, null, null, PriceContainer.VatStatus.INCLUDED, true, true); + PaymentSpecification spec = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "blabla", new CustomerName("a", "b", "c", true), "", null, Locale.ENGLISH, false, false, orderSummary, null, null, PriceContainer.VatStatus.INCLUDED, true, true); PaymentResult result = ticketReservationManager.performPayment(spec, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); assertTrue(result.isSuccessful()); assertEquals(0, eventRepository.findStatisticsFor(event.getId()).getDynamicAllocation()); } @Test - public void testAssignTicketToWaitingQueueUnboundedCategory() { + void testAssignTicketToWaitingQueueUnboundedCategory() { LocalDateTime start = LocalDateTime.now(clockProvider.getClock()).minusMinutes(1); LocalDateTime end = LocalDateTime.now(clockProvider.getClock()).plusMinutes(20); List<TicketCategoryModification> categories = Collections.singletonList( @@ -205,11 +205,11 @@ public void testAssignTicketToWaitingQueueUnboundedCategory() { TicketCategory unbounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(AVAILABLE_SEATS - 1); + tr.setQuantity(AVAILABLE_SEATS - 1); tr.setTicketCategoryId(unbounded.getId()); TicketReservationModification tr2 = new TicketReservationModification(); - tr2.setAmount(1); + tr2.setQuantity(1); tr2.setTicketCategoryId(unbounded.getId()); TicketReservationWithOptionalCodeModification multi = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -219,7 +219,7 @@ public void testAssignTicketToWaitingQueueUnboundedCategory() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = ticketReservationManager.performPayment(specification, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); @@ -229,7 +229,7 @@ public void testAssignTicketToWaitingQueueUnboundedCategory() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscountSingle = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCostSingle = priceAndDiscountSingle.getLeft(); assertTrue(priceAndDiscountSingle.getRight().isEmpty()); - specification = new PaymentSpecification(reservationIdSingle, null, reservationCostSingle.getPriceWithVAT(), + specification = new PaymentSpecification(reservationIdSingle, null, reservationCostSingle.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult resultSingle = ticketReservationManager.performPayment(specification, reservationCostSingle, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); @@ -240,20 +240,20 @@ public void testAssignTicketToWaitingQueueUnboundedCategory() { assertTrue(waitingQueueManager.subscribe(event, customerJohnDoe(event), "john@doe.com", null, Locale.ENGLISH)); - ticketReservationManager.deleteOfflinePayment(event, reservationIdSingle, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationIdSingle, false, false, false, null); - List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).collect(Collectors.toList()); + List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).toList(); assertEquals(1, subscriptions.size()); Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime> subscriptionDetail = subscriptions.get(0); assertEquals("john@doe.com", subscriptionDetail.getLeft().getEmailAddress()); TicketReservationWithOptionalCodeModification reservation = subscriptionDetail.getMiddle(); assertEquals(Integer.valueOf(unbounded.getId()), reservation.getTicketCategoryId()); - assertEquals(Integer.valueOf(1), reservation.getAmount()); + assertEquals(Integer.valueOf(1), reservation.getQuantity()); assertTrue(subscriptionDetail.getRight().isAfter(ZonedDateTime.now(clockProvider.getClock()))); } @Test - public void testAssignTicketToWaitingQueueBoundedCategory() { + void testAssignTicketToWaitingQueueBoundedCategory() { LocalDateTime start = LocalDateTime.now(clockProvider.getClock()).minusMinutes(2); LocalDateTime end = LocalDateTime.now(clockProvider.getClock()).plusMinutes(20); List<TicketCategoryModification> categories = Collections.singletonList( @@ -269,11 +269,11 @@ public void testAssignTicketToWaitingQueueBoundedCategory() { TicketCategory bounded = ticketCategoryRepository.findAllTicketCategories(event.getId()).get(0); TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(AVAILABLE_SEATS - 1); + tr.setQuantity(AVAILABLE_SEATS - 1); tr.setTicketCategoryId(bounded.getId()); TicketReservationModification tr2 = new TicketReservationModification(); - tr2.setAmount(1); + tr2.setQuantity(1); tr2.setTicketCategoryId(bounded.getId()); TicketReservationWithOptionalCodeModification multi = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); @@ -283,7 +283,7 @@ public void testAssignTicketToWaitingQueueBoundedCategory() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = ticketReservationManager.performPayment(specification, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); @@ -293,7 +293,7 @@ public void testAssignTicketToWaitingQueueBoundedCategory() { Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscountSingle = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCostSingle = priceAndDiscountSingle.getLeft(); assertTrue(priceAndDiscountSingle.getRight().isEmpty()); - specification = new PaymentSpecification(reservationIdSingle, null, reservationCostSingle.getPriceWithVAT(), + specification = new PaymentSpecification(reservationIdSingle, null, reservationCostSingle.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult resultSingle = ticketReservationManager.performPayment(specification, reservationCostSingle, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); @@ -302,20 +302,20 @@ public void testAssignTicketToWaitingQueueBoundedCategory() { assertTrue(waitingQueueManager.subscribe(event, customerJohnDoe(event), "john@doe.com", null, Locale.ENGLISH)); - ticketReservationManager.deleteOfflinePayment(event, reservationIdSingle, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationIdSingle, false, false, false, null); - List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).collect(Collectors.toList()); + List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).toList(); assertEquals(1, subscriptions.size()); Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime> subscriptionDetail = subscriptions.get(0); assertEquals("john@doe.com", subscriptionDetail.getLeft().getEmailAddress()); TicketReservationWithOptionalCodeModification reservation = subscriptionDetail.getMiddle(); assertEquals(Integer.valueOf(bounded.getId()), reservation.getTicketCategoryId()); - assertEquals(Integer.valueOf(1), reservation.getAmount()); + assertEquals(Integer.valueOf(1), reservation.getQuantity()); assertTrue(subscriptionDetail.getRight().isAfter(ZonedDateTime.now(clockProvider.getClock()))); } @Test - public void testAssignTicketToWaitingQueueUnboundedCategorySelected() { + void testAssignTicketToWaitingQueueUnboundedCategorySelected() { LocalDateTime start = LocalDateTime.now(clockProvider.getClock()).minusHours(1); LocalDateTime end = LocalDateTime.now(clockProvider.getClock()).plusHours(1); @@ -338,11 +338,11 @@ public void testAssignTicketToWaitingQueueUnboundedCategorySelected() { TicketCategory second = ticketCategories.get(1); TicketReservationModification tr2 = new TicketReservationModification(); - tr2.setAmount(1); + tr2.setQuantity(1); tr2.setTicketCategoryId(second.getId()); TicketReservationModification tr3 = new TicketReservationModification(); - tr3.setAmount(1); + tr3.setQuantity(1); tr3.setTicketCategoryId(first.getId()); reserveTickets(event, first, AVAILABLE_SEATS - 2); @@ -355,36 +355,36 @@ public void testAssignTicketToWaitingQueueUnboundedCategorySelected() { assertTrue(waitingQueueManager.subscribe(event, customerJohnDoe(event), "john@doe.com", first.getId(), Locale.ENGLISH)); assertTrue(waitingQueueManager.subscribe(event, new CustomerName("John Doe 2", "John", "Doe 2", event.mustUseFirstAndLastName()), "john@doe2.com", second.getId(), Locale.ENGLISH)); - ticketReservationManager.deleteOfflinePayment(event, reservationIdSingleFirst, false, false, null); - ticketReservationManager.deleteOfflinePayment(event, reservationIdSingleSecond, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationIdSingleFirst, false, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationIdSingleSecond, false, false, false,null); - List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).collect(Collectors.toList()); + List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).toList(); assertEquals(2, subscriptions.size()); Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime> subscriptionDetail = subscriptions.get(0); assertEquals("john@doe.com", subscriptionDetail.getLeft().getEmailAddress()); TicketReservationWithOptionalCodeModification reservation = subscriptionDetail.getMiddle(); assertEquals(Integer.valueOf(first.getId()), reservation.getTicketCategoryId()); - assertEquals(Integer.valueOf(1), reservation.getAmount()); + assertEquals(Integer.valueOf(1), reservation.getQuantity()); assertTrue(subscriptionDetail.getRight().isAfter(ZonedDateTime.now(clockProvider.getClock()))); subscriptionDetail = subscriptions.get(1); assertEquals("john@doe2.com", subscriptionDetail.getLeft().getEmailAddress()); reservation = subscriptionDetail.getMiddle(); assertEquals(Integer.valueOf(second.getId()), reservation.getTicketCategoryId()); - assertEquals(Integer.valueOf(1), reservation.getAmount()); + assertEquals(Integer.valueOf(1), reservation.getQuantity()); assertTrue(subscriptionDetail.getRight().isAfter(ZonedDateTime.now(clockProvider.getClock()))); } private String reserveTickets(Event event, TicketCategory category, int num) { TicketReservationModification tr = new TicketReservationModification(); - tr.setAmount(num); + tr.setQuantity(num); tr.setTicketCategoryId(category.getId()); TicketReservationWithOptionalCodeModification tcm = new TicketReservationWithOptionalCodeModification(tr, Optional.empty()); String reservationId = ticketReservationManager.createTicketReservation(event, Collections.singletonList(tcm), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Locale.ENGLISH, false, null); Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = ticketReservationManager.totalReservationCostWithVAT(reservationId); TotalPrice reservationCost = priceAndDiscount.getLeft(); assertTrue(priceAndDiscount.getRight().isEmpty()); - PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.getPriceWithVAT(), + PaymentSpecification specification = new PaymentSpecification(reservationId, null, reservationCost.priceWithVAT(), event, "email@example.com", new CustomerName("full name", "full", "name", event.mustUseFirstAndLastName()), "billing address", null, Locale.ENGLISH, true, false, null, "IT", "123456", PriceContainer.VatStatus.INCLUDED, true, false); PaymentResult result = ticketReservationManager.performPayment(specification, reservationCost, PaymentProxy.OFFLINE, PaymentMethod.BANK_TRANSFER, null); @@ -393,7 +393,7 @@ private String reserveTickets(Event event, TicketCategory category, int num) { } @Test - public void testNoPublicCategoryAvailable() { + void testNoPublicCategoryAvailable() { LocalDateTime start = LocalDateTime.now(clockProvider.getClock()).minusHours(1); LocalDateTime end = LocalDateTime.now(clockProvider.getClock()).plusHours(1); @@ -423,12 +423,12 @@ public void testNoPublicCategoryAvailable() { assertTrue(ticketCategoryResult.isSuccess()); assertEquals(1, ticketRepository.countReleasedTicketInCategory(event.getId(), first.getId()).intValue()); //now we should have an extra available ticket - List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).collect(Collectors.toList()); + List<Triple<WaitingQueueSubscription, TicketReservationWithOptionalCodeModification, ZonedDateTime>> subscriptions = waitingQueueManager.distributeSeats(event).toList(); assertEquals(1, subscriptions.size()); } @Test - public void testTicketBelongsToExpiredCategory() { + void testTicketBelongsToExpiredCategory() { LocalDateTime start = LocalDateTime.now(clockProvider.getClock()).minusHours(1); LocalDateTime end = LocalDateTime.now(clockProvider.getClock()).plusHours(1); @@ -453,10 +453,10 @@ public void testTicketBelongsToExpiredCategory() { ticketCategoryRepository.update(first.getId(), first.getName(), first.getInception(event.getZoneId()), event.now(clockProvider).minusMinutes(1L), first.getMaxTickets(), first.isAccessRestricted(), MonetaryUtil.unitToCents(first.getPrice(), first.getCurrencyCode()), first.getCode(), null, null, null, null, first.getTicketCheckInStrategy(), first.getTicketAccessType()); - List<Integer> ticketIds = ticketRepository.findTicketsInReservation(reservationId).stream().map(Ticket::getId).collect(Collectors.toList()); + List<Integer> ticketIds = ticketRepository.findTicketsInReservation(reservationId).stream().map(Ticket::getId).toList(); assertEquals(2, ticketIds.size()); - ticketReservationManager.deleteOfflinePayment(event, reservationId, false, false, null); + ticketReservationManager.deleteOfflinePayment(event, reservationId, false, false, false, null); List<TicketInfo> releasedButExpired = ticketRepository.findReleasedBelongingToExpiredCategories(event.getId(), event.now(clockProvider)); assertEquals(2, releasedButExpired.size()); diff --git a/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java b/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java index 88cd2714b8..2acd55d9c8 100644 --- a/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java +++ b/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java @@ -35,6 +35,7 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -61,11 +62,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class WaitingQueueProcessorIntegrationTest extends BaseIntegrationTest { +class WaitingQueueProcessorIntegrationTest extends BaseIntegrationTest { private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); diff --git a/src/test/java/alfio/manager/WaitingQueueProcessorMultiThreadedIntegrationTest.java b/src/test/java/alfio/manager/WaitingQueueProcessorMultiThreadedIntegrationTest.java index c242604fae..ace59a0146 100644 --- a/src/test/java/alfio/manager/WaitingQueueProcessorMultiThreadedIntegrationTest.java +++ b/src/test/java/alfio/manager/WaitingQueueProcessorMultiThreadedIntegrationTest.java @@ -36,6 +36,7 @@ import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.ClockProvider; import org.apache.commons.lang3.StringUtils; @@ -59,10 +60,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -public class WaitingQueueProcessorMultiThreadedIntegrationTest { +class WaitingQueueProcessorMultiThreadedIntegrationTest { private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); diff --git a/src/test/java/alfio/manager/i18n/MessageSourceManagerIntegrationTest.java b/src/test/java/alfio/manager/i18n/MessageSourceManagerIntegrationTest.java index 6ac5a57f92..3864fedde6 100644 --- a/src/test/java/alfio/manager/i18n/MessageSourceManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/i18n/MessageSourceManagerIntegrationTest.java @@ -30,6 +30,7 @@ import alfio.repository.EventRepository; import alfio.repository.system.ConfigurationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.test.util.IntegrationTestUtil; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; @@ -53,11 +54,10 @@ import static alfio.test.util.IntegrationTestUtil.initEvent; import static org.junit.jupiter.api.Assertions.assertEquals; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class MessageSourceManagerIntegrationTest extends BaseIntegrationTest { +class MessageSourceManagerIntegrationTest extends BaseIntegrationTest { @Autowired diff --git a/src/test/java/alfio/manager/payment/StripeConnectManagerTest.java b/src/test/java/alfio/manager/payment/StripeConnectManagerTest.java index a872dbb198..a85604badd 100644 --- a/src/test/java/alfio/manager/payment/StripeConnectManagerTest.java +++ b/src/test/java/alfio/manager/payment/StripeConnectManagerTest.java @@ -60,7 +60,7 @@ void getConnectURLWithCustomState() { when(configurationManager.getFor(anyCollection(), any(ConfigurationLevel.class))).thenReturn(map); when(extensionManager.generateOAuth2StateParam(anyInt())).thenReturn(Optional.of(state)); AuthorizationRequestDetails connectURL = stripeConnectManager.getConnectURL(1); - assertEquals(state, connectURL.getState()); + assertEquals(state, connectURL.state()); } @Test @@ -74,6 +74,6 @@ void getConnectURLWithStandardState() { when(configurationManager.getFor(anyCollection(), any(ConfigurationLevel.class))).thenReturn(map); when(extensionManager.generateOAuth2StateParam(anyInt())).thenReturn(Optional.empty()); AuthorizationRequestDetails connectURL = stripeConnectManager.getConnectURL(1); - assertNotEquals(state, connectURL.getState()); + assertNotEquals(state, connectURL.state()); } } \ No newline at end of file diff --git a/src/test/java/alfio/manager/payment/StripeWebhookPaymentManagerTest.java b/src/test/java/alfio/manager/payment/StripeWebhookPaymentManagerTest.java index a8263950ce..87b3347e99 100644 --- a/src/test/java/alfio/manager/payment/StripeWebhookPaymentManagerTest.java +++ b/src/test/java/alfio/manager/payment/StripeWebhookPaymentManagerTest.java @@ -20,6 +20,7 @@ import alfio.manager.system.ConfigurationLevel; import alfio.manager.system.ConfigurationManager; import alfio.manager.system.ConfigurationManager.MaybeConfiguration; +import alfio.manager.testSupport.MaybeConfigurationBuilder; import alfio.model.Audit; import alfio.model.Event; import alfio.model.TicketReservation; @@ -30,8 +31,8 @@ import alfio.repository.system.ConfigurationRepository; import alfio.test.util.TestUtil; import com.stripe.model.Charge; -import com.stripe.model.ChargeCollection; import com.stripe.model.PaymentIntent; +import com.stripe.net.RequestOptions; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,7 +51,7 @@ class StripeWebhookPaymentManagerTest { - private static final String RESERVATION_ID = "abcdefg"; + private static final String RESERVATION_ID = "RESERVATION_ID"; private static final String PAYMENT_ID = "PAYMENT_ID"; private static final int EVENT_ID = 11; private static final int TRANSACTION_ID = 22; @@ -67,10 +68,12 @@ class StripeWebhookPaymentManagerTest { private AuditingRepository auditingRepository; private Environment environment; private TicketReservation ticketReservation; + private BaseStripeManager baseStripeManager; + private static final String SK_LIVE = "sk_live_"; private static final MaybeConfiguration STRIPE_SECRET_KEY_CONF = new MaybeConfiguration(ConfigurationKeys.STRIPE_SECRET_KEY, - new ConfigurationKeyValuePathLevel(null, "sk_live_", null)); + new ConfigurationKeyValuePathLevel(null, SK_LIVE, null)); @BeforeEach void setup() { @@ -92,6 +95,9 @@ void setup() { ticketReservation = mock(TicketReservation.class); when(ticketReservation.getId()).thenReturn(RESERVATION_ID); when(eventRepository.findByReservationId(eq(RESERVATION_ID))).thenReturn(event); + baseStripeManager = mock(BaseStripeManager.class); + when(baseStripeManager.getSecretKey(any())).thenReturn(SK_LIVE); + when(baseStripeManager.options(any())).thenReturn(Optional.of(RequestOptions.builder().build())); stripeWebhookPaymentManager = new StripeWebhookPaymentManager(configurationManager, ticketRepository, transactionRepository, configurationRepository, ticketReservationRepository, eventRepository, auditingRepository, environment, TestUtil.clockProvider()); } @@ -105,26 +111,27 @@ void ignoreNotRelevantTypes() { } @Test - void transactionSucceeded() { + void transactionSucceeded() throws Exception { var paymentIntent = mock(PaymentIntent.class); var transactionWebhookPayload = mock(TransactionWebhookPayload.class); when(transactionWebhookPayload.getType()).thenReturn("payment_intent.succeeded"); when(transactionWebhookPayload.getPayload()).thenReturn(paymentIntent); when(paymentIntent.getMetadata()).thenReturn(Map.of(MetadataBuilder.RESERVATION_ID, RESERVATION_ID)); when(paymentIntent.getStatus()).thenReturn(BaseStripeManager.SUCCEEDED); - var chargeCollection = mock(ChargeCollection.class); - when(paymentIntent.getCharges()).thenReturn(chargeCollection); var charge = mock(Charge.class); - when(chargeCollection.getData()).thenReturn(List.of(charge)); + when(paymentIntent.getLatestCharge()).thenReturn(CHARGE_ID); + when(baseStripeManager.retrieveCharge(eq(CHARGE_ID), any())).thenReturn(charge); when(charge.getId()).thenReturn(CHARGE_ID); when(ticketReservationRepository.findOptionalReservationById(eq(RESERVATION_ID))).thenReturn(Optional.of(ticketReservation)); when(ticketReservation.getStatus()).thenReturn(TicketReservation.TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT); var paymentContext = mock(PaymentContext.class); when(paymentContext.getPurchaseContext()).thenReturn(event); when(configurationManager.getFor(eq(STRIPE_SECRET_KEY), any())).thenReturn(STRIPE_SECRET_KEY_CONF); + when(configurationManager.getFor(eq(PLATFORM_MODE_ENABLED), any())).thenReturn(MaybeConfigurationBuilder.missing(PLATFORM_MODE_ENABLED)); when(paymentIntent.getLivemode()).thenReturn(true); when(transactionRepository.updateIfStatus(eq(TRANSACTION_ID), eq(CHARGE_ID), eq(PAYMENT_ID), any(), eq(0L), eq(0L), eq(Transaction.Status.COMPLETE), eq(Map.of()), eq(Transaction.Status.PENDING))).thenReturn(1); - var paymentWebhookResult = stripeWebhookPaymentManager.processWebhook(transactionWebhookPayload, transaction, paymentContext); + var customWebHookPaymentManager = new StripeWebhookPaymentManager(configurationManager, transactionRepository, ticketReservationRepository, eventRepository, auditingRepository, TestUtil.clockProvider(), baseStripeManager); + var paymentWebhookResult = customWebHookPaymentManager.processWebhook(transactionWebhookPayload, transaction, paymentContext); assertEquals(PaymentWebhookResult.Type.SUCCESSFUL, paymentWebhookResult.getType()); verify(transactionRepository).updateIfStatus(eq(TRANSACTION_ID), eq(CHARGE_ID), eq(PAYMENT_ID), any(), eq(0L), eq(0L), eq(Transaction.Status.COMPLETE), eq(Map.of()), eq(Transaction.Status.PENDING)); Map<String, Object> changes = Map.of("paymentId", CHARGE_ID, "paymentMethod", "stripe"); @@ -132,26 +139,26 @@ void transactionSucceeded() { } @Test - void transactionAlreadyConfirmed() { + void transactionAlreadyConfirmed() throws Exception { var paymentIntent = mock(PaymentIntent.class); var transactionWebhookPayload = mock(TransactionWebhookPayload.class); when(transactionWebhookPayload.getType()).thenReturn("payment_intent.succeeded"); when(transactionWebhookPayload.getPayload()).thenReturn(paymentIntent); when(paymentIntent.getMetadata()).thenReturn(Map.of(MetadataBuilder.RESERVATION_ID, RESERVATION_ID)); when(paymentIntent.getStatus()).thenReturn(BaseStripeManager.SUCCEEDED); - var chargeCollection = mock(ChargeCollection.class); - when(paymentIntent.getCharges()).thenReturn(chargeCollection); var charge = mock(Charge.class); - when(chargeCollection.getData()).thenReturn(List.of(charge)); - when(charge.getId()).thenReturn(CHARGE_ID); + when(paymentIntent.getLatestCharge()).thenReturn(CHARGE_ID); + when(baseStripeManager.retrieveCharge(eq(CHARGE_ID), any())).thenReturn(charge); when(ticketReservationRepository.findOptionalReservationById(eq(RESERVATION_ID))).thenReturn(Optional.of(ticketReservation)); when(ticketReservation.getStatus()).thenReturn(TicketReservation.TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT); var paymentContext = mock(PaymentContext.class); when(paymentContext.getPurchaseContext()).thenReturn(event); when(configurationManager.getFor(eq(STRIPE_SECRET_KEY), any())).thenReturn(STRIPE_SECRET_KEY_CONF); + when(configurationManager.getFor(eq(PLATFORM_MODE_ENABLED), any())).thenReturn(MaybeConfigurationBuilder.missing(PLATFORM_MODE_ENABLED)); when(paymentIntent.getLivemode()).thenReturn(true); when(transactionRepository.updateIfStatus(eq(TRANSACTION_ID), eq(CHARGE_ID), eq(PAYMENT_ID), any(), eq(0L), eq(0L), eq(Transaction.Status.COMPLETE), eq(Map.of()), eq(Transaction.Status.PENDING))).thenReturn(0); - var paymentWebhookResult = stripeWebhookPaymentManager.processWebhook(transactionWebhookPayload, transaction, paymentContext); + var customWebHookPaymentManager = new StripeWebhookPaymentManager(configurationManager, transactionRepository, ticketReservationRepository, eventRepository, auditingRepository, TestUtil.clockProvider(), baseStripeManager); + var paymentWebhookResult = customWebHookPaymentManager.processWebhook(transactionWebhookPayload, transaction, paymentContext); assertEquals(PaymentWebhookResult.Type.SUCCESSFUL, paymentWebhookResult.getType()); verify(transactionRepository).updateIfStatus(eq(TRANSACTION_ID), eq(CHARGE_ID), eq(PAYMENT_ID), any(), eq(0L), eq(0L), eq(Transaction.Status.COMPLETE), eq(Map.of()), eq(Transaction.Status.PENDING)); Map<String, Object> changes = Map.of("paymentId", CHARGE_ID, "paymentMethod", "stripe"); @@ -191,10 +198,8 @@ void doNotAcceptTestEventsOnLiveEnv() { when(transactionWebhookPayload.getPayload()).thenReturn(paymentIntent); when(paymentIntent.getMetadata()).thenReturn(Map.of(MetadataBuilder.RESERVATION_ID, RESERVATION_ID)); when(paymentIntent.getStatus()).thenReturn(BaseStripeManager.SUCCEEDED); - var chargeCollection = mock(ChargeCollection.class); - when(paymentIntent.getCharges()).thenReturn(chargeCollection); var charge = mock(Charge.class); - when(chargeCollection.getData()).thenReturn(List.of(charge)); + when(paymentIntent.getLatestChargeObject()).thenReturn(charge); when(charge.getId()).thenReturn(CHARGE_ID); when(ticketReservationRepository.findOptionalReservationById(eq(RESERVATION_ID))).thenReturn(Optional.of(ticketReservation)); when(ticketReservation.getStatus()).thenReturn(TicketReservation.TicketReservationStatus.EXTERNAL_PROCESSING_PAYMENT); diff --git a/src/test/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilderTest.java b/src/test/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilderTest.java index ee8abacd4e..2ee0dff206 100644 --- a/src/test/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilderTest.java +++ b/src/test/java/alfio/manager/payment/saferpay/PaymentPageAssertRequestBuilderTest.java @@ -19,12 +19,14 @@ import com.google.gson.JsonParser; import org.junit.jupiter.api.Test; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; class PaymentPageAssertRequestBuilderTest { @Test - void buildRequest() { + void buildRequest() throws IOException { String json = new PaymentPageAssertRequestBuilder("token", 1) .addAuthentication("customerId", "requestId") .build(); diff --git a/src/test/java/alfio/manager/payment/saferpay/PaymentPageInitializeBuilderTest.java b/src/test/java/alfio/manager/payment/saferpay/PaymentPageInitializeBuilderTest.java index 4f62800284..443a8f252e 100644 --- a/src/test/java/alfio/manager/payment/saferpay/PaymentPageInitializeBuilderTest.java +++ b/src/test/java/alfio/manager/payment/saferpay/PaymentPageInitializeBuilderTest.java @@ -27,6 +27,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.io.IOException; + import static alfio.manager.payment.saferpay.PaymentPageInitializeRequestBuilder.SUPPORTED_METHODS; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -49,7 +51,7 @@ public void init() { } @Test - void buildInitializeRequest() { + void buildInitializeRequest() throws IOException { var prb = new PaymentPageInitializeRequestBuilder("http://localhost", paymentSpecification) .addAuthentication("customerId", "requestId", "terminalId") .addOrderInformation("orderId", "1", "CHF", "description", 1) diff --git a/src/test/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilderTest.java b/src/test/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilderTest.java index 27f6fa10d2..dbac0ba5b2 100644 --- a/src/test/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilderTest.java +++ b/src/test/java/alfio/manager/payment/saferpay/TransactionCaptureRequestBuilderTest.java @@ -19,13 +19,15 @@ import com.google.gson.JsonParser; import org.junit.jupiter.api.Test; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; class TransactionCaptureRequestBuilderTest { @Test - void transactionCaptureRequestBuilder() { + void transactionCaptureRequestBuilder() throws IOException { String json = new TransactionCaptureRequestBuilder("token", 1) .addAuthentication("customerId", "requestId") .build(); @@ -37,7 +39,7 @@ void transactionCaptureRequestBuilder() { } @Test - void transactionInquireRequestBuilder() { + void transactionInquireRequestBuilder() throws IOException { String json = new TransactionInquireRequestBuilder("token", 1) .addAuthentication("customerId", "requestId") .build(); diff --git a/src/test/java/alfio/manager/payment/saferpay/TransactionRefundBuilderTest.java b/src/test/java/alfio/manager/payment/saferpay/TransactionRefundBuilderTest.java index 290f7b0e73..1431c53f6f 100644 --- a/src/test/java/alfio/manager/payment/saferpay/TransactionRefundBuilderTest.java +++ b/src/test/java/alfio/manager/payment/saferpay/TransactionRefundBuilderTest.java @@ -19,12 +19,14 @@ import com.google.gson.JsonParser; import org.junit.jupiter.api.Test; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; class TransactionRefundBuilderTest { @Test - void build() { + void build() throws IOException { var json = new TransactionRefundBuilder("captureId", 1) .addAuthentication("customerId", "requestId") .build("100", "CHF"); diff --git a/src/test/java/alfio/manager/support/reservation/ReservationCostCalculatorTest.java b/src/test/java/alfio/manager/support/reservation/ReservationCostCalculatorTest.java new file mode 100644 index 0000000000..3917adbefe --- /dev/null +++ b/src/test/java/alfio/manager/support/reservation/ReservationCostCalculatorTest.java @@ -0,0 +1,193 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.support.reservation; + +import alfio.manager.PurchaseContextManager; +import alfio.model.*; +import alfio.repository.*; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ReservationCostCalculatorTest { + + private static final String TICKET_RESERVATION_ID = "abcdef"; + private Event event; + private Ticket ticket; + private TicketCategory ticketCategory; + private TicketReservation reservation; + private EventRepository eventRepository; + private TicketReservationRepository ticketReservationRepository; + private TicketRepository ticketRepository; + private TicketCategoryRepository ticketCategoryRepository; + private AdditionalServiceItemRepository additionalServiceItemRepository; + private AdditionalServiceRepository additionalServiceRepository; + private AdditionalServiceTextRepository additionalServiceTextRepository; + private ReservationCostCalculator calculator; + + @BeforeEach + void setUp() { + ticketRepository = mock(TicketRepository.class); + ticketReservationRepository = mock(TicketReservationRepository.class); + eventRepository = mock(EventRepository.class); + reservation = mock(TicketReservation.class); + event = mock(Event.class); + when(event.getCurrency()).thenReturn("CHF"); + when(event.event()).thenReturn(Optional.of(event)); + ticket = mock(Ticket.class); + when(ticket.getCurrencyCode()).thenReturn("CHF"); + when(ticket.getCategoryId()).thenReturn(1); + ticketCategory = mock(TicketCategory.class); + when(ticketCategory.getId()).thenReturn(1); + ticketCategoryRepository = mock(TicketCategoryRepository.class); + additionalServiceRepository = mock(AdditionalServiceRepository.class); + additionalServiceItemRepository = mock(AdditionalServiceItemRepository.class); + additionalServiceTextRepository = mock(AdditionalServiceTextRepository.class); + var purchaseContextManager = mock(PurchaseContextManager.class); + when(purchaseContextManager.findByReservationId(anyString())).thenReturn(Optional.of(event)); + calculator = new ReservationCostCalculator( + ticketReservationRepository, + purchaseContextManager, + mock(PromoCodeDiscountRepository.class), + mock(SubscriptionRepository.class), + ticketRepository, + additionalServiceRepository, + additionalServiceItemRepository + ); + } + + @Test + void calcReservationCostOnlyTickets() { + when(event.isVatIncluded()).thenReturn(true, false); + when(event.getVat()).thenReturn(BigDecimal.TEN); + when(eventRepository.findByReservationId(eq(TICKET_RESERVATION_ID))).thenReturn(event); + when(ticketReservationRepository.findReservationById(eq(TICKET_RESERVATION_ID))).thenReturn(reservation); + when(reservation.getId()).thenReturn(TICKET_RESERVATION_ID); + when(ticket.getSrcPriceCts()).thenReturn(10); + when(ticketRepository.findTicketsInReservation(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.singletonList(ticket)); + AdditionalServiceItemRepository additionalServiceItemRepository = mock(AdditionalServiceItemRepository.class); + when(additionalServiceItemRepository.findByReservationUuid(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.emptyList()); + + when(event.getVatStatus()).thenReturn(PriceContainer.VatStatus.INCLUDED); + Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = calculator.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + TotalPrice included = priceAndDiscount.getLeft(); + Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); + Assertions.assertEquals(10, included.priceWithVAT()); + Assertions.assertEquals(1, included.VAT()); + + when(event.getVatStatus()).thenReturn(PriceContainer.VatStatus.NOT_INCLUDED); + Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscountNotIncluded = calculator.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + TotalPrice notIncluded = priceAndDiscountNotIncluded.getLeft(); + Assertions.assertTrue(priceAndDiscountNotIncluded.getRight().isEmpty()); + Assertions.assertEquals(11, notIncluded.priceWithVAT()); + Assertions.assertEquals(1, notIncluded.VAT()); + } + + @Test + void calcReservationCostWithASVatIncludedInherited() { + initReservationWithAdditionalServices(true, AdditionalService.VatType.INHERITED, 10, 10); + //first: event price vat included, additional service VAT inherited + Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = calculator.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + TotalPrice first = priceAndDiscount.getLeft(); + Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); + Assertions.assertEquals(20, first.priceWithVAT()); + Assertions.assertEquals(2, first.VAT()); + } + + @Test + void calcReservationCostWithASVatIncludedASNoVat() { + initReservationWithAdditionalServices(true, AdditionalService.VatType.NONE, 10, 10); + //second: event price vat included, additional service VAT n/a + Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = calculator.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + TotalPrice second = priceAndDiscount.getLeft(); + Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); + Assertions.assertEquals(20, second.priceWithVAT()); + Assertions.assertEquals(1, second.VAT()); + } + + @Test + void calcReservationCostWithASVatNotIncludedASInherited() { + initReservationWithAdditionalServices(false, AdditionalService.VatType.INHERITED, 10, 10); + //third: event price vat not included, additional service VAT inherited + Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = calculator.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + TotalPrice third = priceAndDiscount.getLeft(); + Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); + Assertions.assertEquals(22, third.priceWithVAT()); + Assertions.assertEquals(2, third.VAT()); + } + + @Test + void calcReservationCostWithASVatNotIncludedASNone() { + initReservationWithAdditionalServices(false, AdditionalService.VatType.NONE, 10, 10); + //fourth: event price vat not included, additional service VAT n/a + Pair<TotalPrice, Optional<PromoCodeDiscount>> priceAndDiscount = calculator.totalReservationCostWithVAT(TICKET_RESERVATION_ID); + TotalPrice fourth = priceAndDiscount.getLeft(); + Assertions.assertTrue(priceAndDiscount.getRight().isEmpty()); + Assertions.assertEquals(21, fourth.priceWithVAT()); + Assertions.assertEquals(1, fourth.VAT()); + } + + private void initReservationWithTicket(int ticketPaidPrice, boolean eventVatIncluded) { + when(event.isVatIncluded()).thenReturn(eventVatIncluded); + when(event.getVatStatus()).thenReturn(eventVatIncluded ? PriceContainer.VatStatus.INCLUDED : PriceContainer.VatStatus.NOT_INCLUDED); + when(event.getVat()).thenReturn(BigDecimal.TEN); + when(event.getId()).thenReturn(1); + when(eventRepository.findByReservationId(eq(TICKET_RESERVATION_ID))).thenReturn(event); + when(ticketReservationRepository.findReservationById(eq(TICKET_RESERVATION_ID))).thenReturn(reservation); + when(ticket.getSrcPriceCts()).thenReturn(ticketPaidPrice); + when(ticket.getCategoryId()).thenReturn(1); + when(ticketRepository.findTicketsInReservation(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.singletonList(ticket)); + when(ticketCategoryRepository.getByIdAndActive(eq(1), eq(1))).thenReturn(ticketCategory); + when(ticketCategoryRepository.getByIdsAndActive(anyCollection(), eq(1))).thenReturn(List.of(ticketCategory)); + when(reservation.getId()).thenReturn(TICKET_RESERVATION_ID); + } + + private void initReservationWithAdditionalServices(boolean eventVatIncluded, AdditionalService.VatType additionalServiceVatType, int ticketSrcPrice, int asSrcPrice) { + + initReservationWithTicket(ticketSrcPrice, eventVatIncluded); + + AdditionalServiceItem additionalServiceItem = mock(AdditionalServiceItem.class); + when(additionalServiceItem.getCurrencyCode()).thenReturn("CHF"); + AdditionalService additionalService = mock(AdditionalService.class); + when(additionalService.getCurrencyCode()).thenReturn("CHF"); + when(additionalService.getId()).thenReturn(1); + + when(additionalServiceItemRepository.findByReservationUuid(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.singletonList(additionalServiceItem)); + when(additionalServiceItem.getAdditionalServiceId()).thenReturn(1); + when(additionalServiceRepository.loadAllForEvent(eq(1))).thenReturn(List.of(additionalService)); + when(additionalServiceRepository.getById(eq(1), eq(1))).thenReturn(additionalService); + when(additionalServiceItem.getSrcPriceCts()).thenReturn(asSrcPrice); + when(additionalService.getVatType()).thenReturn(additionalServiceVatType); + AdditionalServiceItemRepository additionalServiceItemRepository = mock(AdditionalServiceItemRepository.class); + when(additionalServiceItemRepository.findByReservationUuid(eq(TICKET_RESERVATION_ID))).thenReturn(Collections.emptyList()); + AdditionalServiceText text = mock(AdditionalServiceText.class); + when(text.id()).thenReturn(1); + when(text.locale()).thenReturn("en"); + when(additionalServiceTextRepository.findBestMatchByLocaleAndType(anyInt(), eq("en"), eq(AdditionalServiceText.TextType.TITLE))).thenReturn(text); + } + +} \ No newline at end of file diff --git a/src/test/java/alfio/manager/system/AdminJobManagerInvoker.java b/src/test/java/alfio/manager/system/AdminJobManagerInvoker.java index b0984156e0..24094531bb 100644 --- a/src/test/java/alfio/manager/system/AdminJobManagerInvoker.java +++ b/src/test/java/alfio/manager/system/AdminJobManagerInvoker.java @@ -28,4 +28,8 @@ public AdminJobManagerInvoker(AdminJobManager adminJobManager) { public void invokeProcessPendingExtensionRetry(ZonedDateTime timestamp) { adminJobManager.processPendingExtensionRetry(timestamp); } + + public void invokeProcessPendingReservationsRetry(ZonedDateTime timestamp) { + adminJobManager.processPendingReservationsRetry(timestamp); + } } diff --git a/src/test/java/alfio/manager/system/BaseMailerTest.java b/src/test/java/alfio/manager/system/BaseMailerTest.java new file mode 100644 index 0000000000..50583fe3f3 --- /dev/null +++ b/src/test/java/alfio/manager/system/BaseMailerTest.java @@ -0,0 +1,133 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.system; + +import alfio.model.Configurable; +import alfio.model.user.Organization; +import alfio.repository.user.OrganizationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import static alfio.manager.testSupport.MaybeConfigurationBuilder.existing; +import static alfio.manager.testSupport.MaybeConfigurationBuilder.missing; +import static alfio.model.system.ConfigurationKeys.MAIL_REPLY_TO; +import static alfio.model.system.ConfigurationKeys.MAIL_SET_ORG_REPLY_TO; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +class BaseMailerTest { + + private static final String ORG_EMAIL = "org@example.org"; + private OrganizationRepository organizationRepository; + private Organization organization; + private BaseMailer baseMailer; + + @BeforeEach + void setUp() { + organizationRepository = mock(OrganizationRepository.class); + organization = mock(Organization.class); + when(organization.getEmail()).thenReturn(ORG_EMAIL); + baseMailer = new BaseMailerMock(organizationRepository); + } + + @Test + void setReplyToOrgEmailWhenEnabled() { + var config = Map.of( + MAIL_REPLY_TO, missing(MAIL_REPLY_TO), + MAIL_SET_ORG_REPLY_TO, existing(MAIL_SET_ORG_REPLY_TO, "true") + ); + int orgId = 1; + when(organizationRepository.getById(orgId)).thenReturn(organization); + var consumer = new AtomicReference<String>(); + baseMailer.setReplyToIfPresent(config, orgId, consumer::set); + assertEquals(ORG_EMAIL, consumer.get()); + consumer.set(null); + // try again to test a cache hit + baseMailer.setReplyToIfPresent(config, orgId, consumer::set); + assertEquals(ORG_EMAIL, consumer.get()); + + verify(organizationRepository, times(1)).getById(orgId); + } + + @Test + void doNotSetOrgEmailWhenReplyToIsAlreadySet() { + String customEmail = "org2@example.org"; + var config = Map.of( + MAIL_REPLY_TO, existing(MAIL_REPLY_TO, customEmail), + MAIL_SET_ORG_REPLY_TO, existing(MAIL_SET_ORG_REPLY_TO, "true") + ); + int orgId = 2; + when(organizationRepository.getById(orgId)).thenReturn(organization); + var consumer = new AtomicReference<String>(); + baseMailer.setReplyToIfPresent(config, orgId, consumer::set); + assertEquals(customEmail, consumer.get()); + verify(organizationRepository, never()).getById(anyInt()); + } + + @Test + void doNotSetOrgEmailWhenNotEnabled() { + int orgId = 3; + var config = Map.of( + MAIL_REPLY_TO, missing(MAIL_REPLY_TO), + MAIL_SET_ORG_REPLY_TO, missing(MAIL_SET_ORG_REPLY_TO) + ); + when(organizationRepository.getById(orgId)).thenReturn(organization); + var consumer = new AtomicReference<String>(); + baseMailer.setReplyToIfPresent(config, orgId, consumer::set); + assertNull(consumer.get()); + verify(organizationRepository, never()).getById(anyInt()); + } + + @Test + void configurationIsRequired() { + // NPE when conf map is null + var exception = assertThrows(NullPointerException.class, () -> baseMailer.setReplyToIfPresent(null, -1, b -> {})); + assertEquals(BaseMailer.MISSING_CONFIG_MESSAGE, exception.getMessage()); + + var missingOrgReplyTo = Map.of( + MAIL_REPLY_TO, missing(MAIL_REPLY_TO) + ); + // NPE when MAIL_SET_ORG_REPLY_TO is missing + exception = assertThrows(NullPointerException.class, () -> baseMailer.setReplyToIfPresent(missingOrgReplyTo, -1, b -> {})); + assertTrue(exception.getMessage().startsWith(MAIL_SET_ORG_REPLY_TO.name())); + + var missingReplyTo = Map.of( + MAIL_SET_ORG_REPLY_TO, missing(MAIL_SET_ORG_REPLY_TO) + ); + // NPE when MAIL_REPLY_TO is missing + exception = assertThrows(NullPointerException.class, () -> baseMailer.setReplyToIfPresent(missingReplyTo, -1, b -> {})); + assertTrue(exception.getMessage().startsWith(MAIL_REPLY_TO.name())); + + } + + private static class BaseMailerMock extends BaseMailer { + BaseMailerMock(OrganizationRepository organizationRepository) { + super(organizationRepository); + } + + @Override + public void send(Configurable configurable, String fromName, String to, List<String> cc, String subject, String text, Optional<String> html, Attachment... attachment) { + throw new IllegalStateException("no can do"); + } + } +} \ No newline at end of file diff --git a/src/test/java/alfio/manager/system/ConfigurationManagerTest.java b/src/test/java/alfio/manager/system/ConfigurationManagerTest.java index c7ca1fb296..a995f0415f 100644 --- a/src/test/java/alfio/manager/system/ConfigurationManagerTest.java +++ b/src/test/java/alfio/manager/system/ConfigurationManagerTest.java @@ -16,7 +16,6 @@ */ package alfio.manager.system; -import alfio.model.PurchaseContext; import alfio.model.system.Configuration; import alfio.model.system.ConfigurationKeys; import org.junit.jupiter.api.Assertions; @@ -24,10 +23,8 @@ import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static alfio.model.system.ConfigurationPathLevel.*; -import static org.mockito.Mockito.when; public class ConfigurationManagerTest { @@ -35,7 +32,7 @@ public class ConfigurationManagerTest { public void testUnionEvent() { Map<ConfigurationKeys.SettingCategory, List<Configuration>> intersection = ConfigurationManager.union(SYSTEM, EVENT); Assertions.assertNotNull(intersection); - List<Configuration> values = intersection.values().stream().flatMap(List::stream).collect(Collectors.toList()); + List<Configuration> values = intersection.values().stream().flatMap(List::stream).toList(); Assertions.assertTrue(ConfigurationKeys.byPathLevel(EVENT).stream().allMatch(k -> values.stream().anyMatch(v -> v.getConfigurationKey() == k && v.getConfigurationPathLevel() == EVENT))); Assertions.assertTrue(values.stream().anyMatch(v -> v.getConfigurationKey() == ConfigurationKeys.BASE_URL && v.getConfigurationPathLevel() == SYSTEM)); } @@ -44,7 +41,7 @@ public void testUnionEvent() { public void testUnionOrganization() { Map<ConfigurationKeys.SettingCategory, List<Configuration>> intersection = ConfigurationManager.union(EVENT, ORGANIZATION); Assertions.assertNotNull(intersection); - List<Configuration> values = intersection.values().stream().flatMap(List::stream).collect(Collectors.toList()); + List<Configuration> values = intersection.values().stream().flatMap(List::stream).toList(); Assertions.assertTrue(ConfigurationKeys.byPathLevel(EVENT).stream().allMatch(k -> values.stream().anyMatch(v -> v.getConfigurationKey() == k && v.getConfigurationPathLevel() == EVENT))); Assertions.assertTrue(values.stream().anyMatch(v -> v.getConfigurationKey() == ConfigurationKeys.VAT_NR && v.getConfigurationPathLevel() == ORGANIZATION)); } diff --git a/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java b/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java index c00f12f54e..da15744fce 100644 --- a/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java +++ b/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java @@ -37,6 +37,7 @@ import alfio.repository.TicketReservationRepository; import alfio.repository.system.EventMigrationRepository; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import org.apache.commons.lang3.time.DateUtils; @@ -57,10 +58,10 @@ import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -public class DataMigratorIntegrationTest extends BaseIntegrationTest { +class DataMigratorIntegrationTest extends BaseIntegrationTest { private static final int AVAILABLE_SEATS = 20; private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc"); @@ -99,7 +100,7 @@ private Pair<Event,String> initEvent(List<TicketCategoryModification> categories organizationRepository.create(organizationName, "org", "email@example.com", null, null); Organization organization = organizationRepository.findByName(organizationName).get(); - userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.OPERATOR, User.Type.INTERNAL); + userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.OPERATOR, User.Type.INTERNAL, null); Map<String, String> desc = new HashMap<>(); desc.put("en", "muh description"); @@ -118,7 +119,7 @@ private Pair<Event,String> initEvent(List<TicketCategoryModification> categories } @Test - public void testMigration() { + void testMigration() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -147,7 +148,7 @@ public void testMigration() { } @Test - public void testMigrationWithExistingRecord() { + void testMigrationWithExistingRecord() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -176,7 +177,7 @@ public void testMigrationWithExistingRecord() { } @Test - public void testAlreadyMigratedEvent() { + void testAlreadyMigratedEvent() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -206,7 +207,7 @@ public void testAlreadyMigratedEvent() { } @Test - public void testUpdateDisplayName() { + void testUpdateDisplayName() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -232,7 +233,7 @@ public void testUpdateDisplayName() { } @Test - public void testUpdateTicketReservation() { + void testUpdateTicketReservation() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -242,7 +243,7 @@ public void testUpdateTicketReservation() { Event event = eventUsername.getKey(); try { TicketReservationModification trm = new TicketReservationModification(); - trm.setAmount(1); + trm.setQuantity(1); trm.setTicketCategoryId(eventManager.loadTicketCategories(event).get(0).getId()); TicketReservationWithOptionalCodeModification r = new TicketReservationWithOptionalCodeModification(trm, Optional.empty()); Date expiration = DateUtils.addDays(new Date(), 1); @@ -256,7 +257,7 @@ public void testUpdateTicketReservation() { } @Test - public void testFixCategoriesSize() { + void testFixCategoriesSize() { List<TicketCategoryModification> categories = Arrays.asList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS -1, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -279,7 +280,7 @@ public void testFixCategoriesSize() { } @Test - public void testFixStuckTickets() { + void testFixStuckTickets() { List<TicketCategoryModification> categories = Collections.singletonList( new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, AVAILABLE_SEATS, new DateTimeModification(LocalDate.now(ClockProvider.clock()), LocalTime.now(ClockProvider.clock())), @@ -288,7 +289,7 @@ public void testFixStuckTickets() { Pair<Event, String> eventUsername = initEvent(categories); Event event = eventUsername.getKey(); TicketReservationModification trm = new TicketReservationModification(); - trm.setAmount(1); + trm.setQuantity(1); trm.setTicketCategoryId(eventManager.loadTicketCategories(event).get(0).getId()); TicketReservationWithOptionalCodeModification r = new TicketReservationWithOptionalCodeModification(trm, Optional.empty()); Date expiration = DateUtils.addDays(new Date(), 1); diff --git a/src/test/java/alfio/manager/system/MailjetMailerTest.java b/src/test/java/alfio/manager/system/MailjetMailerTest.java index 77c893fd73..2aa390d7ef 100644 --- a/src/test/java/alfio/manager/system/MailjetMailerTest.java +++ b/src/test/java/alfio/manager/system/MailjetMailerTest.java @@ -16,36 +16,30 @@ */ package alfio.manager.system; -import alfio.manager.testSupport.MaybeConfigurationBuilder; import alfio.model.Configurable; +import alfio.repository.user.OrganizationRepository; import alfio.util.HttpUtils; import alfio.util.Json; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import javax.net.ssl.SSLSession; -import java.net.URI; import java.net.http.HttpClient; -import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Flow; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static alfio.manager.testSupport.MaybeConfigurationBuilder.existing; +import static alfio.manager.testSupport.MaybeConfigurationBuilder.missing; import static alfio.model.system.ConfigurationKeys.*; -import static alfio.model.system.ConfigurationKeys.MAIL_REPLY_TO; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -64,14 +58,16 @@ void setUp() { configurable = mock(Configurable.class); when(configurable.getConfigurationLevel()).thenReturn(ConfigurationLevel.system()); var configurationManager = mock(ConfigurationManager.class); - when(configurationManager.getFor(eq(EnumSet.of(MAILJET_APIKEY_PUBLIC, MAILJET_APIKEY_PRIVATE, MAILJET_FROM, MAIL_REPLY_TO)), any())) + when(configurationManager.getFor(eq(EnumSet.of(MAILJET_APIKEY_PUBLIC, MAILJET_APIKEY_PRIVATE, MAILJET_FROM, MAIL_REPLY_TO, MAIL_SET_ORG_REPLY_TO)), any())) .thenReturn(Map.of( MAILJET_APIKEY_PUBLIC, existing(MAILJET_APIKEY_PUBLIC, "public"), MAILJET_APIKEY_PRIVATE, existing(MAILJET_APIKEY_PRIVATE, "private"), MAILJET_FROM, existing(MAILJET_FROM, "mail_from"), - MAIL_REPLY_TO, existing(MAIL_REPLY_TO, "mail_to") + MAIL_REPLY_TO, existing(MAIL_REPLY_TO, "mail_to"), + MAIL_SET_ORG_REPLY_TO, missing(MAIL_SET_ORG_REPLY_TO) )); - mailjetMailer = new MailjetMailer(httpClient, configurationManager); + var organizationRepository = mock(OrganizationRepository.class); + mailjetMailer = new MailjetMailer(httpClient, configurationManager, organizationRepository); requestCaptor = ArgumentCaptor.forClass(HttpRequest.class); } diff --git a/src/test/java/alfio/manager/system/ReservationPriceCalculatorTest.java b/src/test/java/alfio/manager/system/ReservationPriceCalculatorTest.java index 36ac843236..e0e6d189c4 100644 --- a/src/test/java/alfio/manager/system/ReservationPriceCalculatorTest.java +++ b/src/test/java/alfio/manager/system/ReservationPriceCalculatorTest.java @@ -46,6 +46,7 @@ void setUp() { reservation = mock(TicketReservation.class); when(reservation.getCurrencyCode()).thenReturn("CHF"); ticket = mock(Ticket.class); + when(ticket.getCurrencyCode()).thenReturn("CHF"); tickets = List.of(ticket); additionalServiceItem = mock(AdditionalServiceItem.class); additionalServiceItems = List.of(additionalServiceItem); @@ -56,11 +57,12 @@ void setUp() { } @Nested - public class PromoCodeDiscountTest { + class PromoCodeDiscountTest { private PromoCodeDiscount discount = mock(PromoCodeDiscount.class); @BeforeEach void init() { + when(ticket.getSrcPriceCts()).thenReturn(10_00); when(ticket.getDiscountCts()).thenReturn(100); when(additionalServiceItem.getDiscountCts()).thenReturn(100); } @@ -87,7 +89,7 @@ void discountOnItems() { } @Nested - public class TaxCalculatorTest { + class TaxCalculatorTest { @BeforeEach void init() { when(ticket.getSrcPriceCts()).thenReturn(1000); diff --git a/src/test/java/alfio/manager/system/SendGridMailerTest.java b/src/test/java/alfio/manager/system/SendGridMailerTest.java index cbf868076d..b11f26fc57 100644 --- a/src/test/java/alfio/manager/system/SendGridMailerTest.java +++ b/src/test/java/alfio/manager/system/SendGridMailerTest.java @@ -16,10 +16,12 @@ */ package alfio.manager.system; +import alfio.manager.testSupport.MaybeConfigurationBuilder; import alfio.model.EventAndOrganizationId; import alfio.model.system.ConfigurationKeyValuePathLevel; import alfio.model.system.ConfigurationKeys; import alfio.model.system.ConfigurationPathLevel; +import alfio.repository.user.OrganizationRepository; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,7 +41,7 @@ import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.Mockito.*; -public class SendGridMailerTest { +class SendGridMailerTest { private SendGridMailer sendGridMailer; @@ -51,16 +53,21 @@ public class SendGridMailerTest { public void setUp() { configurationManager = mock(ConfigurationManager.class); client = mock(HttpClient.class); - sendGridMailer = new SendGridMailer(client, configurationManager); + sendGridMailer = new SendGridMailer(client, configurationManager, mock(OrganizationRepository.class)); } @Test - public void shouldSendEmail() throws IOException, InterruptedException { + void shouldSendEmail() throws IOException, InterruptedException { //Test data final var apiConfig = new ConfigurationManager.MaybeConfiguration(ConfigurationKeys.SENDGRID_API_KEY, new ConfigurationKeyValuePathLevel("key", "value", ConfigurationPathLevel.SYSTEM)); final var fromConfig = new ConfigurationManager.MaybeConfiguration(ConfigurationKeys.SENDGRID_FROM, new ConfigurationKeyValuePathLevel("key", "value", ConfigurationPathLevel.SYSTEM)); //Mock - when(configurationManager.getFor(anySet(), any(ConfigurationLevel.class))).thenReturn(Map.of(ConfigurationKeys.SENDGRID_API_KEY, apiConfig, ConfigurationKeys.SENDGRID_FROM, fromConfig)); + when(configurationManager.getFor(anySet(), any(ConfigurationLevel.class))).thenReturn(Map.of( + ConfigurationKeys.SENDGRID_API_KEY, apiConfig, + ConfigurationKeys.SENDGRID_FROM, fromConfig, + ConfigurationKeys.MAIL_REPLY_TO, MaybeConfigurationBuilder.missing(ConfigurationKeys.MAIL_REPLY_TO), + ConfigurationKeys.MAIL_SET_ORG_REPLY_TO, MaybeConfigurationBuilder.missing(ConfigurationKeys.MAIL_SET_ORG_REPLY_TO) + )); final HttpResponse<Object> httpResponse = createMockHttpResponse(); when(client.send(any(HttpRequest.class), any())).thenReturn(httpResponse); //Service call diff --git a/src/test/java/alfio/manager/wallet/EventTicketClassTest.java b/src/test/java/alfio/manager/wallet/EventTicketClassTest.java new file mode 100644 index 0000000000..86c74496fb --- /dev/null +++ b/src/test/java/alfio/manager/wallet/EventTicketClassTest.java @@ -0,0 +1,56 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class EventTicketClassTest { + + @Test + void build() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + + EventTicketClass object = EventTicketClass.builder() + .id("ISSUER_ID.EVENT_CLASS_ID") + .eventOrGroupingId("1") + .eventName("Devoxx Belgium 2022") + .ticketType("Speaker") + .logoUri("https://reg.devoxx.be/file/c73315d4890c5fade112165c40338b72271e43939eed4e1e57a4e4891ee19cc8") + .venue("Antwerp Kinepolis Belgium") + .start(ZonedDateTime.of(2022, Month.OCTOBER.getValue(), 10, 8, 0, 0, 0, ZoneId.of("UTC"))) + .end(ZonedDateTime.of(2022, Month.OCTOBER.getValue(), 14, 14, 0, 0, 0, ZoneId.of("UTC"))) + .build(); + String build = object.build(objectMapper); + + var resource = getClass().getResource("/wallet-json/event-class.json"); + assertNotNull(resource); + var payload = Files.readString(Path.of(resource.toURI())); + assertEquals(objectMapper.readTree(resource), objectMapper.readTree(build)); + } + +} \ No newline at end of file diff --git a/src/test/java/alfio/manager/wallet/EventTicketObjectTest.java b/src/test/java/alfio/manager/wallet/EventTicketObjectTest.java new file mode 100644 index 0000000000..b4ae00e99e --- /dev/null +++ b/src/test/java/alfio/manager/wallet/EventTicketObjectTest.java @@ -0,0 +1,51 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.manager.wallet; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class EventTicketObjectTest { + + @Test + void build() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + + EventTicketObject object = EventTicketObject.builder() + .id("ISSUER_ID.MEMBER_ID") + .classId("ISSUER_ID.EVENT_CLASS_ID") + .ticketHolderName("Alf") + .ticketNumber("123-321-000") + .barcode("123456789") + .build(); + String build = object.build(objectMapper); + + var resource = getClass().getResource("/wallet-json/event-object.json"); + assertNotNull(resource); + var payload = Files.readString(Path.of(resource.toURI())); + assertEquals(objectMapper.readTree(resource), objectMapper.readTree(build)); + } + +} \ No newline at end of file diff --git a/src/test/java/alfio/model/ContentLanguageTest.java b/src/test/java/alfio/model/ContentLanguageTest.java index 29c88853f7..af1791408d 100644 --- a/src/test/java/alfio/model/ContentLanguageTest.java +++ b/src/test/java/alfio/model/ContentLanguageTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.Test; import java.util.Comparator; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -28,8 +27,7 @@ class ContentLanguageTest { @Test void validateContentLanguages() { var sortedContentLanguages = ContentLanguage.ALL_LANGUAGES.stream() - .sorted(Comparator.comparing(ContentLanguage::getValue)) - .collect(Collectors.toList()); + .sorted(Comparator.comparing(ContentLanguage::getValue)).toList(); for(int i = 1; i < sortedContentLanguages.size(); i++) { var current = sortedContentLanguages.get(i); diff --git a/src/test/java/alfio/model/PriceContainerTest.java b/src/test/java/alfio/model/PriceContainerTest.java index 5ab0c0c408..321a4ca565 100644 --- a/src/test/java/alfio/model/PriceContainerTest.java +++ b/src/test/java/alfio/model/PriceContainerTest.java @@ -24,7 +24,6 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -162,8 +161,7 @@ void totalPriceDoesNotIncludeVatIfSplitPayment() { private Stream<Pair<Integer, PriceContainer>> generateTestStream(PriceContainer.VatStatus vatStatus) { List<BigDecimal> vatPercentages = IntStream.range(100, 3000) - .mapToObj(vatCts -> new BigDecimal(vatCts).divide(new BigDecimal("100.00"), 2, RoundingMode.UNNECESSARY)) - .collect(Collectors.toList()); + .mapToObj(vatCts -> new BigDecimal(vatCts).divide(new BigDecimal("100.00"), 2, RoundingMode.UNNECESSARY)).toList(); return IntStream.range(1, 5_000). parallel() .boxed() diff --git a/src/test/java/alfio/model/TicketFieldConfigurationDescriptionAndValueTest.java b/src/test/java/alfio/model/TicketFieldConfigurationDescriptionAndValueTest.java new file mode 100644 index 0000000000..a056e4426c --- /dev/null +++ b/src/test/java/alfio/model/TicketFieldConfigurationDescriptionAndValueTest.java @@ -0,0 +1,88 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; +import org.mockito.Mockito; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TicketFieldConfigurationDescriptionAndValueTest { + + private TicketFieldConfiguration configuration; + private TicketFieldDescription description; + + @BeforeEach + void setUp() { + configuration = mock(TicketFieldConfiguration.class); + description = mock(TicketFieldDescription.class); + } + + @ParameterizedTest + @ValueSource(strings = {"text", + "tel", + "textarea", + "vat:eu"}) + void getValueDescriptionForTextField(String type) { + var field = new TicketFieldConfigurationDescriptionAndValue(configuration, description, 1, "simple value"); + when(configuration.getType()).thenReturn(type); + assertEquals("simple value", field.getValueDescription()); + } + + @ParameterizedTest + @ValueSource(strings = {"select", "radio"}) + void getValueDescriptionForSingleOptionField(String type) { + when(description.getRestrictedValuesDescription()).thenReturn(Map.of("value1", "simple value")); + when(configuration.getRestrictedValues()).thenReturn(List.of("value1", "value2")); + var field = new TicketFieldConfigurationDescriptionAndValue(configuration, description, 1, "value1"); + when(configuration.getType()).thenReturn(type); + assertEquals("simple value", field.getValueDescription()); + } + + @ParameterizedTest + @ArgumentsSource(CheckboxArgumentProvider.class) + void getValueDescriptionForMultipleOptionsField(String value, String expectedResult) { + when(description.getRestrictedValuesDescription()).thenReturn(Map.of("value1", "first value", "value2", "second value")); + when(configuration.getRestrictedValues()).thenReturn(List.of("value1", "value2")); + var field = new TicketFieldConfigurationDescriptionAndValue(configuration, description, 1, value); + when(configuration.getType()).thenReturn("checkbox"); + when(configuration.isCheckboxField()).thenReturn(true); + assertEquals(expectedResult, field.getValueDescription()); + } + + static class CheckboxArgumentProvider implements ArgumentsProvider { + @Override + public Stream<? extends Arguments> provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of("[\"value1\",\"value2\"]", "first value, second value"), + Arguments.of("[\"value1\"]", "first value"), + Arguments.of("[\"value2\"]", "second value"), + Arguments.of("[\"value1\",\"null\",\"value2\"]", "first value, second value") + ); + } + } +} \ No newline at end of file diff --git a/src/test/java/alfio/model/TicketTest.java b/src/test/java/alfio/model/TicketTest.java new file mode 100644 index 0000000000..fd2c5d43c9 --- /dev/null +++ b/src/test/java/alfio/model/TicketTest.java @@ -0,0 +1,79 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model; + +import alfio.util.checkin.NameNormalizer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TicketTest { + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void ticketCode(boolean caseInsensitive) { + var fullName = "Full Name"; + var email = "email@example.org"; + var regularCase = Ticket.generateHmacTicketInfo("eventKey", caseInsensitive, fullName, email, "id", "uuid"); + var modifiedCase = Ticket.generateHmacTicketInfo("eventKey", caseInsensitive, fullName.toUpperCase(), email.toUpperCase(), "id", "uuid"); + assertEquals(caseInsensitive, regularCase.equals(modifiedCase)); + } + + @ParameterizedTest + @ValueSource(strings = { + // format: UPPERCASE/lowercase,UPPERCASE/lowercase... + "A/a,B/b,C/c,D/d,E/e,F/f,G/g,H/h,I/i,J/j,K/k,L/l,M/m,N/n,O/o,P/p,Q/q,R/r,S/s,T/t,U/u,V/v,W/w,X/x,Y/y,Z/z", + "Α/α,Β/β,Γ/γ,Δ/δ,Ε/ε,Ζ/ζ,Η/η,Θ/θ,Ι/ι,Κ/κ,Λ/λ,Μ/μ,Ν/ν,Ξ/ξ,Ο/ο,Π/π,Ρ/ρ,Σ/σς,Τ/τ,Υ/υ,Φ/φ,Χ/χ,Ψ/ψ,Ω/ω", //greek, https://en.wikipedia.org/wiki/Greek_alphabet + "Ç/ç,Ğ/ğ,I/i,İ/i,J/j,Ö/ö,Ş/ş,Ü/ü", // turkish https://en.wikipedia.org/wiki/Turkish_alphabet, except character ı, see NameNormalizer + "А/а,Б/б,В/в,Г/г,Д/д,Е/е,Ж/ж,З/з,И/и,Й/й,К/к,Л/л,М/м,Н/н,О/о,П/п,Р/р,С/с,Т/т,У/у,Ф/ф,Х/х,Ц/ц,Ч/ч,Ш/ш,Щ/щ,Ъ/ъ,Ь/ь,Ю/ю,Я/я", // bulgarian, https://en.wikipedia.org/wiki/Bulgarian_alphabet + "Å/å,Ä/ä,Ö/ö", // Swedish https://en.wikipedia.org/wiki/Swedish_alphabet + "Ă/ă,Â/â,I/i,Î/î,Ș/ș,Ț/ț", // Romanian https://en.wikipedia.org/wiki/Romanian_alphabet + "Ä/ä,Ö/ö,Ü/ü,ẞ/ß", // German https://en.wikipedia.org/wiki/German_orthography + "Æ/æ,Ø/ø,Å/å", //Danish / Norwegian https://en.wikipedia.org/wiki/Danish_and_Norwegian_alphabet + "Ą/ą,Ć/ć,Ę/ę,Ł/ł,Ń/ń,Ó/ó,Ś/ś,Ź/ź,Ż/ż", // Polish https://en.wikipedia.org/wiki/Polish_alphabet + "À/à,È/è,É/é,Ì/ì,Ò/ò,Ù/ù", // Italian / French vowels + "万/万,丈/丈,三/三,上/上", // some random characters with no support for lowerCase + "❤/❤,✅/✅" // emojis + }) + void validateCase(String letters) { + var uppercase = new StringBuilder(); + var lowercase = new StringBuilder(); + Arrays.stream(letters.split(",")).forEach(str -> { + var split = str.split("/"); + uppercase.append(split[0]); + lowercase.append(split[1]); + }); + assertEquals(lowercase.toString(), NameNormalizer.normalize(uppercase.toString())); + var unmodifiedLowercase = Ticket.generateHmacTicketInfo("eventKey", false, lowercase.toString(), "mail@example.org", "id", "uuid"); + var processedUppercase = Ticket.generateHmacTicketInfo("eventKey", true, uppercase.toString(), "mail@example.org", "id", "uuid"); + assertEquals(unmodifiedLowercase, processedUppercase); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void ticketCodeUTF8Chars(boolean caseInsensitive) { + var fullName = "çÇĞğIıİiÖöŞşÜüΑ"; + var fullNameSwappedCase = "ÇçğĞıIiİöÖşŞüÜα"; + var email = "email@example.org"; + var regularCase = Ticket.generateHmacTicketInfo("eventKey", caseInsensitive, fullName, email, "id", "uuid"); + var modifiedCase = Ticket.generateHmacTicketInfo("eventKey", caseInsensitive, fullNameSwappedCase, email.toUpperCase(), "id", "uuid"); + assertEquals(caseInsensitive, regularCase.equals(modifiedCase)); + } +} \ No newline at end of file diff --git a/src/test/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmountTest.java b/src/test/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmountTest.java new file mode 100644 index 0000000000..c2c79ed929 --- /dev/null +++ b/src/test/java/alfio/model/modification/PromoCodeDiscountWithFormattedTimeAndAmountTest.java @@ -0,0 +1,58 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.modification; + +import alfio.model.PromoCodeDiscount; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static alfio.test.util.TestUtil.FIXED_TIME_CLOCK; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PromoCodeDiscountWithFormattedTimeAndAmountTest { + + private PromoCodeDiscount promoCodeDiscount; + + @BeforeEach + void setUp() { + promoCodeDiscount = mock(PromoCodeDiscount.class); + } + + @Test + void getFormattedDiscountAmountUseEventCurrencyCode() { + when(promoCodeDiscount.hasCurrencyCode()).thenReturn(true); + when(promoCodeDiscount.getCurrencyCode()).thenReturn("JPY"); + when(promoCodeDiscount.getDiscountType()).thenReturn(PromoCodeDiscount.DiscountType.FIXED_AMOUNT); + when(promoCodeDiscount.getDiscountAmount()).thenReturn(10000); + when(promoCodeDiscount.getFixedAmount()).thenReturn(true); + var pdc = new PromoCodeDiscountWithFormattedTimeAndAmount(promoCodeDiscount, FIXED_TIME_CLOCK.getClock().getZone(), "CHF"); + assertEquals("100.00", pdc.getFormattedDiscountAmount()); + } + + @Test + void getFormattedDiscountAmountUseEmbeddedCurrencyCode() { + when(promoCodeDiscount.hasCurrencyCode()).thenReturn(true); + when(promoCodeDiscount.getCurrencyCode()).thenReturn("JPY"); + when(promoCodeDiscount.getDiscountType()).thenReturn(PromoCodeDiscount.DiscountType.FIXED_AMOUNT); + when(promoCodeDiscount.getDiscountAmount()).thenReturn(10000); + when(promoCodeDiscount.getFixedAmount()).thenReturn(true); + var pdc = new PromoCodeDiscountWithFormattedTimeAndAmount(promoCodeDiscount, FIXED_TIME_CLOCK.getClock().getZone(), null); + assertEquals("10000", pdc.getFormattedDiscountAmount()); + } +} diff --git a/src/test/java/alfio/model/modification/ReservationRequestTest.java b/src/test/java/alfio/model/modification/ReservationRequestTest.java new file mode 100644 index 0000000000..b21ec3a928 --- /dev/null +++ b/src/test/java/alfio/model/modification/ReservationRequestTest.java @@ -0,0 +1,116 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.model.modification; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class ReservationRequestTest { + + @Test + void single() { + var metadata = Map.of("key", "value"); + var request = new DummyReservationRequest(1, List.of(metadata)); + var attendees = request.getAttendees(); + assertEquals(1, attendees.size()); + var attendee = attendees.get(0); + assertNull(attendee.getFirstName()); + assertNull(attendee.getLastName()); + assertNull(attendee.getEmail()); + assertFalse(attendee.hasContactData()); + assertTrue(attendee.hasMetadata()); + assertEquals(metadata, attendee.getMetadata()); + } + + @Test + void multiple() { + var metadata = List.of(Map.of("key1", "value1"), Map.of("key2", "value2")); + var request = new DummyReservationRequest(2, metadata); + var attendees = request.getAttendees(); + assertEquals(2, attendees.size()); + for (int i = 0; i < attendees.size(); i++) { + var attendee = attendees.get(i); + assertNull(attendee.getFirstName()); + assertNull(attendee.getLastName()); + assertNull(attendee.getEmail()); + assertFalse(attendee.hasContactData()); + assertTrue(attendee.hasMetadata()); + assertEquals(metadata.get(i), attendee.getMetadata()); + } + } + + @Test + void multipleWithoutMetadata() { + var metadata = List.of(Map.of("key1", "value1")); + var request = new DummyReservationRequest(2, metadata); + var attendees = request.getAttendees(); + assertEquals(2, attendees.size()); + for (int i = 0; i < attendees.size(); i++) { + var attendee = attendees.get(i); + assertNull(attendee.getFirstName()); + assertNull(attendee.getLastName()); + assertNull(attendee.getEmail()); + assertFalse(attendee.hasContactData()); + if (i == 0) { + assertTrue(attendee.hasMetadata()); + assertNotNull(attendee.getMetadata()); + assertEquals(metadata.get(i), attendee.getMetadata()); + } else { + assertFalse(attendee.hasMetadata()); + } + } + } + + @Test + void multipleMetadataNull() { + var request = new DummyReservationRequest(2, null); + var attendees = request.getAttendees(); + assertEquals(2, attendees.size()); + attendees.forEach(attendeeData -> assertFalse(attendeeData.hasMetadata())); + attendees.forEach(attendeeData -> assertFalse(attendeeData.hasContactData())); + } + + private static class DummyReservationRequest implements ReservationRequest { + + private final Integer quantity; + private final List<Map<String, String>> metadata; + + private DummyReservationRequest(Integer quantity, List<Map<String, String>> metadata) { + this.quantity = quantity; + this.metadata = metadata; + } + + @Override + public Integer getTicketCategoryId() { + return 1; + } + + @Override + public Integer getQuantity() { + return quantity; + } + + @Override + public List<Map<String, String>> getMetadata() { + return metadata; + } + } +} \ No newline at end of file diff --git a/src/test/java/alfio/model/system/ConfigurationKeysTest.java b/src/test/java/alfio/model/system/ConfigurationKeysTest.java index 02043c1b78..c3243ec242 100644 --- a/src/test/java/alfio/model/system/ConfigurationKeysTest.java +++ b/src/test/java/alfio/model/system/ConfigurationKeysTest.java @@ -27,7 +27,7 @@ class ConfigurationKeysTest { @Test void validateAllBooleansHaveDefaultValue() { - var pattern = Pattern.compile(".*?\\(.*?default.*? (true|false).*?\\).*?"); + var pattern = Pattern.compile(".*?\\(.*?default.*?(true|false).*?\\).*?"); Arrays.stream(ConfigurationKeys.values()) .filter(ConfigurationKeys::isBooleanComponentType) .forEach(confKey -> { diff --git a/src/test/java/alfio/repository/EventRepositoryIntegrationTest.java b/src/test/java/alfio/repository/EventRepositoryIntegrationTest.java index c09e81f66f..da7579a439 100644 --- a/src/test/java/alfio/repository/EventRepositoryIntegrationTest.java +++ b/src/test/java/alfio/repository/EventRepositoryIntegrationTest.java @@ -30,6 +30,7 @@ import alfio.model.modification.TicketCategoryModification; import alfio.model.result.Result; import alfio.repository.user.OrganizationRepository; +import alfio.test.util.AlfioIntegrationTest; import alfio.util.BaseIntegrationTest; import alfio.util.ClockProvider; import ch.digitalfondue.npjt.AffectedRowCountAndKey; @@ -57,11 +58,10 @@ import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest +@AlfioIntegrationTest @ContextConfiguration(classes = {DataSourceConfiguration.class, WebSecurityConfig.class, TestConfiguration.class}) @ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST}) -@Transactional -public class EventRepositoryIntegrationTest extends BaseIntegrationTest { +class EventRepositoryIntegrationTest extends BaseIntegrationTest { private static final String NEW_YORK_TZ = "America/New_York"; private static final String ORG_NAME = "name"; diff --git a/src/test/java/alfio/test/util/AlfioIntegrationTest.java b/src/test/java/alfio/test/util/AlfioIntegrationTest.java new file mode 100644 index 0000000000..548d30fe50 --- /dev/null +++ b/src/test/java/alfio/test/util/AlfioIntegrationTest.java @@ -0,0 +1,30 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.test.util; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@SpringBootTest +@ExtendWith(DataCleaner.class) +public @interface AlfioIntegrationTest { +} diff --git a/src/test/java/alfio/test/util/DataCleaner.java b/src/test/java/alfio/test/util/DataCleaner.java new file mode 100644 index 0000000000..160be17ea2 --- /dev/null +++ b/src/test/java/alfio/test/util/DataCleaner.java @@ -0,0 +1,93 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.test.util; + +import alfio.config.authentication.support.APITokenAuthentication; +import alfio.manager.OrganizationDeleter; +import alfio.util.RefreshableDataSource; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.jdbc.UncategorizedSQLException; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static alfio.config.authentication.support.AuthenticationConstants.SYSTEM_API_CLIENT; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DataCleaner implements AfterEachCallback, BeforeEachCallback { + + private static final Logger log = LoggerFactory.getLogger(DataCleaner.class); + private final Set<String> initialConfiguration = new HashSet<>(); + + @Override + public void afterEach(ExtensionContext context) { + if (hasDataSource(context)) { + // test succeeded + var applicationContext = SpringExtension.getApplicationContext(context); + var jdbc = applicationContext.getBean(NamedParameterJdbcTemplate.class); + try { + // delete configuration + assertTrue(jdbc.update("delete from configuration where c_key not in (:keys)", Map.of("keys", initialConfiguration)) >= 0); + assertTrue(jdbc.update("delete from configuration_organization", Map.of()) >= 0); + assertTrue(jdbc.update("delete from configuration_event", Map.of()) >= 0); + assertTrue(jdbc.update("delete from extension_event", Map.of()) >= 0); + assertTrue(jdbc.update("delete from extension_configuration_metadata_value", Map.of()) >= 0); + assertTrue(jdbc.update("delete from extension_configuration_metadata", Map.of()) >= 0); + assertTrue(jdbc.update("delete from extension_log", Map.of()) >= 0); + assertTrue(jdbc.update("delete from extension_support", Map.of()) >= 0); + assertTrue(jdbc.update("delete from admin_job_queue", Map.of()) >= 0); + // delete organization + var organizationDeleter = applicationContext.getBean(OrganizationDeleter.class); + jdbc.queryForList("select id from organization", Map.of(), Integer.class) + .forEach(orgId -> organizationDeleter.deleteOrganization(orgId, new APITokenAuthentication("TEST", "", List.of(new SimpleGrantedAuthority("ROLE_" + SYSTEM_API_CLIENT))))); + assertTrue(jdbc.queryForList("select id from organization", Map.of(), Integer.class).isEmpty()); + jdbc.update("delete from user_profile", Map.of()); + jdbc.update("delete from ba_user", Map.of()); + } catch (UncategorizedSQLException e) { + log.warn("cannot delete data. Connection was already aborted?", e); + } + } + getDataSource(context).refresh(); + } + + @Override + public void beforeEach(ExtensionContext context) { + if (hasDataSource(context)) { + var jdbc = SpringExtension.getApplicationContext(context).getBean(NamedParameterJdbcTemplate.class); + initialConfiguration.addAll(jdbc.queryForList("select c_key from configuration", Map.of(), String.class)); + } + } + + private boolean hasDataSource(ExtensionContext extensionContext) { + var applicationContext = SpringExtension.getApplicationContext(extensionContext); + return applicationContext.getBeanNamesForType(RefreshableDataSource.class).length > 0 + && applicationContext.getBeanNamesForType(NamedParameterJdbcTemplate.class).length > 0; + } + + private RefreshableDataSource getDataSource(ExtensionContext extensionContext) { + return SpringExtension.getApplicationContext(extensionContext).getBean(RefreshableDataSource.class); + } +} diff --git a/src/test/java/alfio/test/util/IntegrationTestUtil.java b/src/test/java/alfio/test/util/IntegrationTestUtil.java index 6cc799c695..fba777305b 100644 --- a/src/test/java/alfio/test/util/IntegrationTestUtil.java +++ b/src/test/java/alfio/test/util/IntegrationTestUtil.java @@ -102,15 +102,26 @@ public static Pair<Event, String> initEvent(List<TicketCategoryModification> cat List<EventModification.AdditionalService> additionalServices, Event.EventFormat eventFormat) { + return initEvent(categories, organizationRepository, userManager, eventManager, eventRepository, additionalServices, eventFormat, PriceContainer.VatStatus.INCLUDED); + } + + public static Pair<Event, String> initEvent(List<TicketCategoryModification> categories, + OrganizationRepository organizationRepository, + UserManager userManager, + EventManager eventManager, + EventRepository eventRepository, + List<EventModification.AdditionalService> additionalServices, + Event.EventFormat eventFormat, + PriceContainer.VatStatus eventVatStatus) { String organizationName = UUID.randomUUID().toString(); String username = UUID.randomUUID().toString(); String eventName = UUID.randomUUID().toString(); var organizationModification = new OrganizationModification(null, organizationName, "email@example.com", "org", null, null); - userManager.createOrganization(organizationModification); + userManager.createOrganization(organizationModification, null); Organization organization = organizationRepository.findByName(organizationName).orElseThrow(); - userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.OPERATOR, User.Type.INTERNAL); - userManager.insertUser(organization.getId(), username+"_owner", "test", "test", "test@example.com", Role.OWNER, User.Type.INTERNAL); + userManager.insertUser(organization.getId(), username, "test", "test", "test@example.com", Role.OPERATOR, User.Type.INTERNAL, null); + userManager.insertUser(organization.getId(), username+"_owner", "test", "test", "test@example.com", Role.OWNER, User.Type.INTERNAL, null); LocalDateTime expiration = LocalDateTime.now(ClockProvider.clock()).plusDays(5).plusHours(1); @@ -120,14 +131,16 @@ public static Pair<Event, String> initEvent(List<TicketCategoryModification> cat desc.put("de", "muh description"); EventModification em = new EventModification(null, eventFormat, "url", "url", "url", "privacy","url", null, - eventName, "event display name", organization.getId(), - "muh location", "0.0", "0.0", ClockProvider.clock().getZone().getId(), desc, - new DateTimeModification(LocalDate.now(ClockProvider.clock()).plusDays(5), LocalTime.now(ClockProvider.clock())), - new DateTimeModification(expiration.toLocalDate(), expiration.toLocalTime()), - BigDecimal.TEN, "CHF", AVAILABLE_SEATS, BigDecimal.ONE, true, Collections.singletonList(PaymentProxy.OFFLINE), categories, false, new LocationDescriptor("","","",""), 7, null, additionalServices, AlfioMetadata.empty(), List.of()); + eventName, "event display name", organization.getId(), + "muh location", "0.0", "0.0", ClockProvider.clock().getZone().getId(), desc, + new DateTimeModification(LocalDate.now(ClockProvider.clock()).plusDays(5), LocalTime.now(ClockProvider.clock())), + new DateTimeModification(expiration.toLocalDate(), expiration.toLocalTime()), + BigDecimal.TEN, "CHF", AVAILABLE_SEATS, BigDecimal.ONE, PriceContainer.VatStatus.isVatIncluded(eventVatStatus), Collections.singletonList(PaymentProxy.OFFLINE), categories, false, new LocationDescriptor("","","",""), 7, null, additionalServices, AlfioMetadata.empty(), List.of()); eventManager.createEvent(em, username); Event event = eventManager.getSingleEvent(eventName, username); Assertions.assertEquals(AVAILABLE_SEATS, eventRepository.countExistingTickets(event.getId()).intValue()); + Assertions.assertTrue(event.mustUseFirstAndLastName()); + Assertions.assertTrue(event.supportsQRCodeCaseInsensitive()); return Pair.of(event, username); } diff --git a/src/test/java/alfio/test/util/TestUtil.java b/src/test/java/alfio/test/util/TestUtil.java index d409839b7c..42533f6b31 100644 --- a/src/test/java/alfio/test/util/TestUtil.java +++ b/src/test/java/alfio/test/util/TestUtil.java @@ -17,18 +17,19 @@ package alfio.test.util; import alfio.util.ClockProvider; -import lombok.experimental.UtilityClass; import java.time.Clock; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -@UtilityClass -public class TestUtil { +public final class TestUtil { public static final ClockProvider FIXED_TIME_CLOCK; + private TestUtil() { + } + static { var todayAt10Sharp = LocalDateTime.now(Clock.systemUTC()) .withHour(10) diff --git a/src/test/java/alfio/util/BaseIntegrationTest.java b/src/test/java/alfio/util/BaseIntegrationTest.java index dc22ce818b..7f07d187ab 100644 --- a/src/test/java/alfio/util/BaseIntegrationTest.java +++ b/src/test/java/alfio/util/BaseIntegrationTest.java @@ -16,19 +16,34 @@ */ package alfio.util; +import alfio.BaseTestConfiguration; +import alfio.config.authentication.support.APITokenAuthentication; +import alfio.manager.OrganizationDeleter; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.test.context.ActiveProfiles; -import java.util.Base64; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import static alfio.config.authentication.support.AuthenticationConstants.SYSTEM_API_CLIENT; import static org.junit.jupiter.api.Assertions.*; @ActiveProfiles(resolver = ActiveTravisProfileResolver.class) -public class BaseIntegrationTest { +public abstract class BaseIntegrationTest { + public static final byte[] ONE_PIXEL_BLACK_GIF = Base64.getDecoder().decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="); public static void testTransferEventToAnotherOrg(int eventId, diff --git a/src/test/java/alfio/util/EventUtilTest.java b/src/test/java/alfio/util/EventUtilTest.java index 3e50336b5f..19fbef4cb2 100644 --- a/src/test/java/alfio/util/EventUtilTest.java +++ b/src/test/java/alfio/util/EventUtilTest.java @@ -44,7 +44,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class EventUtilTest { +class EventUtilTest { private Event event; private SaleableTicketCategory first; diff --git a/src/test/java/alfio/util/ItalianTaxIdValidatorTest.java b/src/test/java/alfio/util/ItalianTaxIdValidatorTest.java index 0c5e7e9418..60ac7d2a19 100644 --- a/src/test/java/alfio/util/ItalianTaxIdValidatorTest.java +++ b/src/test/java/alfio/util/ItalianTaxIdValidatorTest.java @@ -47,7 +47,7 @@ void vatIdValidationSuccess(String number) { } @ParameterizedTest - @ValueSource(strings = { "63828920583", "58148510562", "61579460221" }) + @ValueSource(strings = { "63828920583", "58148510562", "61579460221", "615794602211", "615794602" }) void vatIdValidationFailure(String number) { assertFalse(ItalianTaxIdValidator.validateVatId(number)); } diff --git a/src/test/java/alfio/util/MiscUtilsTest.java b/src/test/java/alfio/util/MiscUtilsTest.java new file mode 100644 index 0000000000..aa81210a9c --- /dev/null +++ b/src/test/java/alfio/util/MiscUtilsTest.java @@ -0,0 +1,36 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.util; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class MiscUtilsTest { + + @Test + void getAtIndexOrNull() { + var listOfTwoElements = List.of("element1", "element2"); + assertEquals("element1", MiscUtils.getAtIndexOrNull(listOfTwoElements, 0)); + assertEquals("element2", MiscUtils.getAtIndexOrNull(listOfTwoElements, 1)); + assertNull(MiscUtils.getAtIndexOrNull(listOfTwoElements, 3)); + assertNull(MiscUtils.getAtIndexOrNull(listOfTwoElements, Integer.MAX_VALUE)); + assertNull(MiscUtils.getAtIndexOrNull(listOfTwoElements, Integer.MIN_VALUE)); + } +} \ No newline at end of file diff --git a/src/test/java/alfio/util/MustacheCustomTagTest.java b/src/test/java/alfio/util/MustacheCustomTagTest.java index c2c0c19bb5..17ef9701e4 100644 --- a/src/test/java/alfio/util/MustacheCustomTagTest.java +++ b/src/test/java/alfio/util/MustacheCustomTagTest.java @@ -87,4 +87,13 @@ public void testHtmlMarkDown() { //for absolute link we add target="_blank" assertEquals("<p>link <a href=\"http://test\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">bla</a> link</p>\n", MustacheCustomTag.renderToHtmlCommonmarkEscaped("link [bla](http://test) link")); } + + @Test + public void acceptOnlyHttpOrHttpsProtocols() { + assertEquals("<p><a href=\"http://google.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">google</a></p>\n", MustacheCustomTag.renderToHtmlCommonmarkEscaped("[google](http://google.com)")); + assertEquals("<p><a href=\"https://google.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">google</a></p>\n", MustacheCustomTag.renderToHtmlCommonmarkEscaped("[google](https://google.com)")); + assertEquals("<p><a>google</a></p>\n", MustacheCustomTag.renderToHtmlCommonmarkEscaped("[google](any:google.com)")); + assertEquals("<p><a>google</a></p>\n", MustacheCustomTag.renderToHtmlCommonmarkEscaped("[google](other:google.com)")); + assertEquals("<p><a>google</a></p>\n", MustacheCustomTag.renderToHtmlCommonmarkEscaped("[google](protocols:/google.com)")); + } } \ No newline at end of file diff --git a/src/test/java/alfio/util/RefreshableDataSource.java b/src/test/java/alfio/util/RefreshableDataSource.java new file mode 100644 index 0000000000..a9a42ae503 --- /dev/null +++ b/src/test/java/alfio/util/RefreshableDataSource.java @@ -0,0 +1,45 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see <http://www.gnu.org/licenses/>. + */ +package alfio.util; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.jdbc.datasource.DelegatingDataSource; + +import javax.sql.DataSource; +import java.util.concurrent.atomic.AtomicReference; + +public class RefreshableDataSource extends DelegatingDataSource { + + private final HikariConfig config; + private final AtomicReference<HikariDataSource> dataSource = new AtomicReference<>(); + + public RefreshableDataSource(HikariConfig config) { + this.config = config; + this.dataSource.set(new HikariDataSource(config)); + } + + @Override + public DataSource getTargetDataSource() { + return dataSource.get(); + } + + public void refresh() { + this.dataSource.getAndSet(new HikariDataSource(config)).close(); + } + +} diff --git a/src/test/java/alfio/util/TemplateResourceTest.java b/src/test/java/alfio/util/TemplateResourceTest.java index b32b61e6ed..283be561e0 100644 --- a/src/test/java/alfio/util/TemplateResourceTest.java +++ b/src/test/java/alfio/util/TemplateResourceTest.java @@ -30,8 +30,7 @@ import static alfio.test.util.TestUtil.clockProvider; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -65,7 +64,7 @@ void buildModelForTicketEmail() { @Test void buildModelForTicketPDF() { Pair<ZonedDateTime, ZonedDateTime> dates = getDates(); - when(ticket.ticketCode(anyString())).thenReturn("abcd"); + when(ticket.ticketCode(anyString(), anyBoolean())).thenReturn("abcd"); when(event.getPrivateKey()).thenReturn("key"); Map<String, Object> model = TemplateResource.buildModelForTicketPDF(organization, event, ticketReservation, ticketCategory, ticketWithMetadata, Optional.empty(), "abcd", Collections.emptyMap()); assertEquals(dates.getLeft(), model.get("validityStart")); @@ -81,7 +80,7 @@ private Pair<ZonedDateTime, ZonedDateTime> getDates() { when(event.getZoneId()).thenReturn(ZoneId.systemDefault()); when(event.getEnd()).thenReturn(eventEnd); when(ticketCategory.getTicketValidityStart(eq(ZoneId.systemDefault()))).thenReturn(validityStart); - when(ticket.ticketCode(anyString())).thenReturn("abcd"); + when(ticket.ticketCode(anyString(), anyBoolean())).thenReturn("abcd"); return Pair.of(validityStart, eventEnd); } diff --git a/src/test/resources/api/descriptor.json b/src/test/resources/api/descriptor.json new file mode 100644 index 0000000000..a7a92c3125 --- /dev/null +++ b/src/test/resources/api/descriptor.json @@ -0,0 +1,34843 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "OpenAPI definition", + "version" : "v0" + }, + "servers" : [ { + "url" : "http://localhost", + "description" : "Generated server url" + } ], + "paths" : { + "/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}" : { + "get" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "getTicketInfo", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketInfo" + } + } + } + } + } + }, + "put" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "updateTicketInfo", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateTicketOwnerForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseBoolean" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "releaseTicket", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v1/admin/system/organization/{id}/api-key" : { + "put" : { + "tags" : [ "organizations-api-v-1-controller" ], + "operationId" : "createApiKeyForOrganization", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationApiKey" + } + } + } + } + } + } + }, + "/api/v1/admin/event/{slug}/subscriptions" : { + "get" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "getLinkedSubscriptions", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/LinkedSubscriptions" + } + } + } + } + } + }, + "put" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "updateLinkedSubscriptions", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "uuid" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/LinkedSubscriptions" + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll/{pollId}" : { + "get" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "getPollDetail", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PollModification" + } + } + } + } + } + }, + "put" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "updateStatus", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdatePollStatusForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PollModification" + } + } + } + } + } + }, + "post" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "updatePoll", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PollModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PollModification" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "deletePoll", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/users/{id}/reset-password" : { + "put" : { + "tags" : [ "users-api-controller" ], + "operationId" : "resetPassword", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "baseUrl", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UserWithPasswordAndQRCode" + } + } + } + } + } + } + }, + "/admin/api/system/api-key" : { + "get" : { + "tags" : [ "system-api-key-api-controller" ], + "operationId" : "retrieveApiKey", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + }, + "put" : { + "tags" : [ "system-api-key-api-controller" ], + "operationId" : "rotateApiKey", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/regenerate-billing-document" : { + "put" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "regenerateBillingDocument", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/notify" : { + "put" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "notifyReservation", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdminReservationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/confirm" : { + "put" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "confirmReservation", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultTicketReservationDescriptor" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/billing-document/{documentId}/restore" : { + "put" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "restoreBillingDocument", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "documentId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/event/{publicIdentifier}/{reservationId}/notify-attendees" : { + "put" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "notifyAttendees", + "parameters" : [ { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/payments/{purchaseContextType}/{publicIdentifier}/reservation/{reservationId}" : { + "put" : { + "tags" : [ "admin-payments-api-controller" ], + "operationId" : "updateTransactionData", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TransactionMetadataModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{id}/status" : { + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "activateEvent", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "active", + "in" : "query", + "required" : true, + "schema" : { + "type" : "boolean" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/rearrange-categories" : { + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "rearrangeCategories", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CategoryOrdinalModification" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/metadata" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "loadMetadata", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/AlfioMetadata" + } + } + } + } + } + }, + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "updateMetadata", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/MetadataModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/category/{categoryId}/unbind-tickets" : { + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "unbindTickets", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/category/{categoryId}/metadata" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "loadCategoryMetadata", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/AlfioMetadata" + } + } + } + } + } + }, + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "updateCategoryMetadata", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/MetadataModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/categories/{categoryId}/tickets/{ticketId}/toggle-locking" : { + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "toggleTicketLocking", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "ticketId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/events/reallocate" : { + "put" : { + "tags" : [ "event-api-controller" ], + "operationId" : "reallocateTickets", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAllocationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/event/{eventName}/waiting-queue/subscriber/{subscriberId}/restore" : { + "put" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "restoreSubscriber", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "subscriberId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + } + } + } + } + }, + "/admin/api/event/{eventName}/waiting-queue/status" : { + "get" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "getStatusForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "put" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "setStatusForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetStatusForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "boolean" + } + } + } + } + } + } + } + }, + "/admin/api/event/{eventId}/additional-services/{additionalServiceId}" : { + "put" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "update", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "additionalServiceId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdditionalService" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/AdditionalService" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "remove", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "additionalServiceId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/configuration/generate-tickets-for-subscriptions" : { + "put" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "generateTicketsForSubscriptions", + "parameters" : [ { + "name" : "eventId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "organizationId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/report-csp-violation" : { + "post" : { + "tags" : [ "csp-report-api-controller" ], + "operationId" : "logCspViolation", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/webhook/stripe/notification" : { + "post" : { + "tags" : [ "webhook-api-controller" ], + "operationId" : "handleStripeMessage", + "parameters" : [ { + "name" : "Stripe-Signature", + "in" : "header", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/webhook/mollie/event/{eventName}/reservation/{reservationId}" : { + "post" : { + "tags" : [ "webhook-api-controller" ], + "operationId" : "handleMollie", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/{purchaseContextType}/{publicIdentifier}/reservation/{reservationId}/re-send-email" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "reSendReservationConfirmationEmail", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "lang", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/user/me" : { + "get" : { + "tags" : [ "user-api-v-2-controller" ], + "operationId" : "getUserIdentity", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + } + } + } + } + }, + "post" : { + "tags" : [ "user-api-v-2-controller" ], + "operationId" : "updateProfile", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateProfileForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseUser" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "user-api-v-2-controller" ], + "operationId" : "deleteCurrentUser", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ClientRedirect" + } + } + } + } + } + } + }, + "/api/v2/public/user/logout" : { + "post" : { + "tags" : [ "user-api-v-2-controller" ], + "operationId" : "logout", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ClientRedirect" + } + } + } + } + } + } + }, + "/api/v2/public/subscription/{id}" : { + "get" : { + "tags" : [ "subscriptions-api-controller" ], + "operationId" : "getSubscriptionInfo", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorWithAdditionalInfo" + } + } + } + } + } + }, + "post" : { + "tags" : [ "subscriptions-api-controller" ], + "operationId" : "reserveSubscription", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseString" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/validate-to-overview" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "validateToOverview", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "lang", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ignoreWarnings", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ContactAndTicketsForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseBoolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/validate-to-overview" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "validateToOverview_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "lang", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ignoreWarnings", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ContactAndTicketsForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseBoolean" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/payment/{method}/init" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "initTransaction", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "allParams", + "in" : "query", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/MultiValueMapStringString" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TransactionInitializationToken" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/payment/{method}/init" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "initTransaction_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "allParams", + "in" : "query", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/MultiValueMapStringString" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TransactionInitializationToken" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/back-to-booking" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "backToBooking", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/back-to-booking" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "backToBooking_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/apply-code" : { + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "applyCode", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationCodeForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseBoolean" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getReservationInfo", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationInfo" + } + } + } + } + } + }, + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "confirmOverview", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "lang", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PaymentForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseReservationPaymentResult" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "cancelPendingReservation", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getReservationInfo_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationInfo" + } + } + } + } + } + }, + "post" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "confirmOverview_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "lang", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PaymentForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseReservationPaymentResult" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "cancelPendingReservation_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/waiting-list/subscribe" : { + "post" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "subscribeToWaitingList", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/WaitingQueueSubscriptionForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseBoolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/send-ticket-by-email" : { + "post" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "sendTicketByEmail", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reserve-tickets" : { + "post" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "reserveTickets", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "lang", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseString" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/poll/{pollId}/answer" : { + "post" : { + "tags" : [ "poll-api-controller" ], + "operationId" : "registerAnswer", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PollVoteForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseBoolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/check-discount" : { + "post" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "checkDiscount", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/DynamicDiscount" + } + } + } + } + } + } + }, + "/api/v1/admin/system/organization/{id}" : { + "get" : { + "tags" : [ "organizations-api-v-1-controller" ], + "operationId" : "getSingleOrganization", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Organization" + } + } + } + } + } + }, + "post" : { + "tags" : [ "organizations-api-v-1-controller" ], + "operationId" : "update_1", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Organization" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "organizations-api-v-1-controller" ], + "operationId" : "delete", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v1/admin/system/organization/create" : { + "post" : { + "tags" : [ "organizations-api-v-1-controller" ], + "operationId" : "createOrganization", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Organization" + } + } + } + } + } + } + }, + "/api/v1/admin/subscription/{subscriptionId}/update" : { + "post" : { + "tags" : [ "subscription-api-v-1-controller" ], + "operationId" : "update_2", + "parameters" : [ { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorModificationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/v1/admin/subscription/{subscriptionId}/events" : { + "get" : { + "tags" : [ "subscription-api-v-1-controller" ], + "operationId" : "getLinkedEvents", + "parameters" : [ { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + } + } + } + }, + "post" : { + "tags" : [ "subscription-api-v-1-controller" ], + "operationId" : "updateLinkedEvents", + "parameters" : [ { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/api/v1/admin/subscription/{id}/reservation" : { + "post" : { + "tags" : [ "reservation-api-v-1-controller" ], + "operationId" : "createSubscriptionReservation", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionReservationCreationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CreationResponse" + } + } + } + } + } + } + }, + "/api/v1/admin/subscription/create" : { + "post" : { + "tags" : [ "subscription-api-v-1-controller" ], + "operationId" : "create", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorModificationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/v1/admin/event/{slug}/reservation" : { + "post" : { + "tags" : [ "reservation-api-v-1-controller" ], + "operationId" : "createTicketsReservation", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketReservationCreationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CreationResponse" + } + } + } + } + } + } + }, + "/api/v1/admin/event/{slug}/generate-subscribers-tickets" : { + "post" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "generateTicketsForSubscribers", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v1/admin/event/update/{slug}" : { + "post" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "update_3", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EventCreationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/v1/admin/event/create" : { + "post" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "create_1", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EventCreationRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/reservation/{reservationId}/payment/{method}/init" : { + "post" : { + "tags" : [ "payment-api-controller" ], + "operationId" : "initTransaction_2", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "allParams", + "in" : "query", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/MultiValueMapStringString" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TransactionInitializationToken" + } + } + } + } + } + } + }, + "/api/events/{eventName}/reservation/{reservationId}/payment/{method}/init" : { + "post" : { + "tags" : [ "payment-api-controller" ], + "operationId" : "initTransaction_3", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "allParams", + "in" : "query", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/MultiValueMapStringString" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TransactionInitializationToken" + } + } + } + } + } + } + }, + "/api/payment/webhook/stripe/payment" : { + "post" : { + "tags" : [ "stripe-payment-webhook-controller" ], + "operationId" : "receivePaymentConfirmation", + "parameters" : [ { + "name" : "Stripe-Signature", + "in" : "header", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/payment/webhook/mollie/reservation/{reservationId}" : { + "post" : { + "tags" : [ "mollie-payment-webhook-controller" ], + "operationId" : "receivePaymentConfirmation_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/pass/event/{eventName}/v1/log" : { + "post" : { + "tags" : [ "pass-kit-api-controller" ], + "operationId" : "log", + "responses" : { + "200" : { + "description" : "OK" + } + } + } + }, + "/api/pass/event/{eventName}/v1/devices/*/registrations/*/*" : { + "post" : { + "tags" : [ "pass-kit-api-controller" ], + "operationId" : "register", + "responses" : { + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "pass-kit-api-controller" ], + "operationId" : "deleteRegistration", + "responses" : { + "200" : { + "description" : "OK" + } + } + } + }, + "/api/attendees/sponsor-scan" : { + "post" : { + "tags" : [ "attendee-api-controller" ], + "operationId" : "scanBadge", + "parameters" : [ { + "name" : "Alfio-Operator", + "in" : "header", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SponsorScanRequest" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + } + }, + "/api/attendees/sponsor-scan/bulk" : { + "post" : { + "tags" : [ "attendee-api-controller" ], + "operationId" : "scanBadges", + "parameters" : [ { + "name" : "Alfio-Operator", + "in" : "header", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SponsorScanRequest" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll" : { + "get" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "getAllForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PollModification" + } + } + } + } + } + } + }, + "post" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "createNewPoll", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PollModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int64" + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll/{pollId}/allow" : { + "post" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "allowAttendees", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateParticipantsForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/utils/short-name/validate" : { + "post" : { + "tags" : [ "utils-api-controller" ], + "operationId" : "validateShortName", + "parameters" : [ { + "name" : "shortName", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/users/{id}/enable/{enable}" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "enableUser", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "enable", + "in" : "path", + "required" : true, + "schema" : { + "type" : "boolean" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/users/new" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "insertUser", + "parameters" : [ { + "name" : "baseUrl", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UserModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UserWithPasswordAndQRCode" + } + } + } + } + } + } + }, + "/admin/api/users/edit" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "editUser", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UserModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/users/current/update-password" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "updateCurrentUserPassword", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PasswordModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/users/current/edit" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "updateCurrentUser", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UserModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/users/check" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "validateUser", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UserModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/resource/" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "findAll", + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/UploadedResource" + } + } + } + } + } + } + }, + "post" : { + "tags" : [ "resource-controller" ], + "operationId" : "uploadFile", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/resource-organization/{organizationId}/" : { + "post" : { + "tags" : [ "resource-controller" ], + "operationId" : "uploadFile_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/resource-event/{organizationId}/{eventId}/" : { + "post" : { + "tags" : [ "resource-controller" ], + "operationId" : "uploadFile_2", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "loadReservation", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultTicketReservationDescriptor" + } + } + } + } + } + }, + "post" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "updateReservation", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdminReservationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/refund" : { + "post" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "refund", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RefundAmount" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/credit" : { + "post" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "creditReservation", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "refund", + "in" : "query", + "required" : true, + "schema" : { + "type" : "boolean" + } + }, { + "name" : "notify", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/cancel" : { + "post" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "removeReservation", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "refund", + "in" : "query", + "required" : true, + "schema" : { + "type" : "boolean" + } + }, { + "name" : "notify", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + }, { + "name" : "issueCreditNote", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultBoolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/new" : { + "post" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "createNew", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdminReservationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultString" + } + } + } + } + } + } + }, + "/admin/api/reservation/event/{publicIdentifier}/{reservationId}/remove-tickets" : { + "post" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "removeTickets", + "parameters" : [ { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RemoveTicketsModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultRemoveResult" + } + } + } + } + } + } + }, + "/admin/api/promo-code" : { + "post" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "addPromoCode", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PromoCodeDiscountModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/promo-code/{promoCodeId}" : { + "post" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "updatePromoCode", + "parameters" : [ { + "name" : "promoCodeId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PromoCodeDiscountModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "removePromoCode", + "parameters" : [ { + "name" : "promoCodeId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/promo-code/{promoCodeId}/disable" : { + "post" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "disablePromoCode", + "parameters" : [ { + "name" : "promoCodeId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/overridable-template/{name}/{locale}/preview" : { + "post" : { + "tags" : [ "resource-controller" ], + "operationId" : "previewTemplate", + "parameters" : [ { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "GOOGLE_ANALYTICS", "CONFIRMATION_EMAIL_FOR_ORGANIZER", "SEND_RESERVED_CODE", "CONFIRMATION_EMAIL", "CONFIRMATION_EMAIL_SUBSCRIPTION", "OFFLINE_RESERVATION_EXPIRED_EMAIL", "CHARGE_ATTEMPT_FAILED_EMAIL", "CHARGE_ATTEMPT_FAILED_EMAIL_FOR_ORGANIZER", "CREDIT_NOTE_ISSUED_EMAIL", "OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER", "OFFLINE_PAYMENT_MATCHES_FOUND", "REMINDER_EMAIL", "REMINDER_TICKET_ADDITIONAL_INFO", "REMINDER_TICKETS_ASSIGNMENT_EMAIL", "TICKET_EMAIL", "TICKET_EMAIL_FOR_ONLINE_EVENT", "TICKET_HAS_CHANGED_OWNER", "TICKET_HAS_BEEN_CANCELLED", "TICKET_HAS_BEEN_CANCELLED_ADMIN", "TICKET_PDF", "RECEIPT_PDF", "INVOICE_PDF", "CREDIT_NOTE_PDF", "SUBSCRIPTION_PDF", "WAITING_QUEUE_JOINED", "WAITING_QUEUE_RESERVATION_EMAIL", "CUSTOM_MESSAGE" ] + } + }, { + "name" : "locale", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "organizationId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "subscriptionDescriptorId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/organizations/validate-slug" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "validateSlug", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/organizations/update" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "updateOrganization", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/organizations/new" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "insertOrganization", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/organizations/check" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "validateOrganization", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/OrganizationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/organization/{organizationId}/subscription/{subscriptionId}" : { + "get" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "getSingle_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorModification" + } + } + } + } + } + }, + "post" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "update_4", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string", + "format" : "uuid" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "deactivate_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/organization/{organizationId}/subscription/" : { + "post" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "create_2", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string", + "format" : "uuid" + } + } + } + } + } + } + }, + "/admin/api/group/{groupId}/link" : { + "post" : { + "tags" : [ "group-api-controller" ], + "operationId" : "linkGroup", + "parameters" : [ { + "name" : "groupId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/LinkedGroupModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}/update/{groupId}" : { + "post" : { + "tags" : [ "group-api-controller" ], + "operationId" : "updateGroup", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "groupId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GroupModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/GroupModification" + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}/new" : { + "post" : { + "tags" : [ "group-api-controller" ], + "operationId" : "createNew_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GroupModification" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/file/upload" : { + "post" : { + "tags" : [ "file-upload-api-controller" ], + "operationId" : "uploadFile_3", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/extensions" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "listAll", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionSupport" + } + } + } + } + } + } + }, + "post" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "create_3", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Extension" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/SerializablePairBooleanString" + } + } + } + } + } + } + }, + "/admin/api/extensions/{path}/{name}" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "loadSingle", + "parameters" : [ { + "name" : "path", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ExtensionSupport" + } + } + } + } + } + }, + "post" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "update_5", + "parameters" : [ { + "name" : "path", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Extension" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/SerializablePairBooleanString" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "delete_5", + "parameters" : [ { + "name" : "path", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/extensions/{path}/{name}/toggle/{enable}" : { + "post" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "toggle", + "parameters" : [ { + "name" : "path", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "enable", + "in" : "path", + "required" : true, + "schema" : { + "type" : "boolean" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/extensions/setting/system/bulk-update" : { + "post" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "bulkUpdateSystem", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionMetadataValue" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/extensions/setting/organization/{orgId}/event/{shortName}/bulk-update" : { + "post" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "bulkUpdateEvent", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "shortName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionMetadataValue" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/extensions/setting/organization/{orgId}/bulk-update" : { + "post" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "bulkUpdateOrganization", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionMetadataValue" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{id}/prices/update" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "updatePrices", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EventModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/events/{id}/header/update" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "updateHeader", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EventModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/pending-payments/{reservationId}/confirm" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "confirmPayment", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TransactionMetadataModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/pending-payments/bulk-confirmation" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "bulkConfirmation", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TripleBooleanStringString" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/messages/send" : { + "post" : { + "tags" : [ "custom-messages-api-controller" ], + "operationId" : "send", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/MessageModification" + } + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/messages/preview" : { + "post" : { + "tags" : [ "custom-messages-api-controller" ], + "operationId" : "preview", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/MessageModification" + } + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/categories/{categoryId}/send-codes" : { + "post" : { + "tags" : [ "special-price-api-controller" ], + "operationId" : "sendCodes", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SendCodeModification" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/categories/{categoryId}/link-codes" : { + "post" : { + "tags" : [ "special-price-api-controller" ], + "operationId" : "linkAssigneeToCodes", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UploadBase64FileModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SendCodeModification" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/capability/{capability}" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "generateUrlForOnlineEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "capability", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "GENERATE_MEETING_LINK", "CREATE_VIRTUAL_ROOM", "CREATE_ANONYMOUS_GUEST_LINK", "CREATE_GUEST_LINK" ] + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/additional-field/{id}" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "updateAdditionalField", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateAdditionalField" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "event-api-controller" ], + "operationId" : "deleteAdditionalField", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/additional-field/swap-position/{id1}/{id2}" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "swapAdditionalFieldPosition", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "id1", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "id2", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/additional-field/set-position/{id}" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "setAdditionalFieldPosition", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "newPosition", + "in" : "query", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/additional-field/new" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "addAdditionalField", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdditionalField" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/additional-field/descriptions" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "saveAdditionalFieldDescriptions", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/TicketFieldDescriptionModification" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventId}/categories/{categoryId}/update" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "updateExistingCategory", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketCategoryModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/events/{eventId}/categories/new" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "createCategory", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketCategoryModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/events/new" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "insertEvent", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EventModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/check" : { + "post" : { + "tags" : [ "event-api-controller" ], + "operationId" : "validateEventRequest", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EventModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/event/{eventName}/attendees/import" : { + "post" : { + "tags" : [ "attendee-bulk-import-api-controller" ], + "operationId" : "createReservations", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "oneReservationPerAttendee", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdminReservationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultString" + } + } + } + } + } + } + }, + "/admin/api/event/{eventId}/additional-services" : { + "get" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "loadAll", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalService" + } + } + } + } + } + } + }, + "post" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "insert", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdditionalService" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/AdditionalService" + } + } + } + } + } + } + }, + "/admin/api/configuration/update" : { + "post" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "updateConfiguration", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ConfigurationModification" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/update-bulk" : { + "post" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "updateConfiguration_1", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConfigurationModification" + } + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/organizations/{organizationId}/update" : { + "post" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "updateOrganizationConfiguration", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConfigurationModification" + } + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/organizations/{organizationId}/events/{eventId}/update" : { + "post" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "updateEventConfiguration", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConfigurationModification" + } + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/events/{eventId}/categories/{categoryId}/update" : { + "post" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "updateCategoryConfiguration", + "parameters" : [ { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConfigurationModification" + } + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/event/{eventId}/regenerate-invoices" : { + "post" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "regenerateInvoices", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int64" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventName}/offline" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "getOfflineEncryptedInfo", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "additionalField", + "in" : "query", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventId}/tickets" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "findAllTicketsForAdminCheckIn", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/FullTicketInfo" + } + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventId}/ticket/{ticketIdentifier}" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "findTicketWithUUID", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "qrCode", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + }, + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "checkIn", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketCode" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventId}/ticket/{ticketIdentifier}/revert-check-in" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "revertCheckIn", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventId}/ticket/{ticketIdentifier}/manual-check-in" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "manualCheckIn", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventId}/ticket/{ticketIdentifier}/confirm-on-site-payment" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "confirmOnSitePayment", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/OnSitePaymentConfirmation" + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{eventName}/ticket/{ticketIdentifier}" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "findTicketWithUUID_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "qrCode", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + }, + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "checkIn_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "offlineUser", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketCode" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{eventName}/ticket/{ticketIdentifier}/revert-check-in" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "revertCheckIn_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{eventName}/ticket/{ticketIdentifier}/manual-check-in" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "manualCheckIn_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{eventName}/ticket/{ticketIdentifier}/confirm-on-site-payment" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "confirmOnSitePayment_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "offlineUser", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/TicketCode" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{eventName}/bulk" : { + "post" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "bulkCheckIn", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "offlineUser", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "forceCheckInPaymentOnSite", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketIdentifierCode" + } + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/TicketAndCheckInResult" + } + } + } + } + } + } + } + }, + "/admin/api/api-keys/bulk" : { + "post" : { + "tags" : [ "users-api-controller" ], + "operationId" : "bulkCreate", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/BulkApiKeyCreation" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/additional-services/validate" : { + "post" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "checkAdditionalService", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AdditionalService" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidationResult" + } + } + } + } + } + } + }, + "/admin/api/organization/{organizationId}/subscription/{subscriptionId}/is-public" : { + "patch" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "setPublicState", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + }, { + "name" : "status", + "in" : "query", + "required" : true, + "schema" : { + "type" : "boolean" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/wallet/event/{eventName}/v1/version/passes/{uuid}" : { + "get" : { + "tags" : [ "google-wallet-api-controller" ], + "operationId" : "walletPass", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "uuid", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/user/reservations" : { + "get" : { + "tags" : [ "user-api-v-2-controller" ], + "operationId" : "getUserReservations", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PurchaseContextWithReservations" + } + } + } + } + } + } + } + }, + "/api/v2/public/user/authentication-enabled" : { + "get" : { + "tags" : [ "user-api-v-2-controller" ], + "operationId" : "userAuthenticationEnabled", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/subscriptions" : { + "get" : { + "tags" : [ "subscriptions-api-controller" ], + "operationId" : "listSubscriptions", + "parameters" : [ { + "name" : "searchOptions", + "in" : "query", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/SearchOptions" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BasicSubscriptionDescriptorInfo" + } + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/transaction/force-check" : { + "get" : { + "tags" : [ "payment-api-controller" ], + "operationId" : "forceCheckStatus", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PaymentResult" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/transaction/force-check" : { + "get" : { + "tags" : [ "payment-api-controller" ], + "operationId" : "forceCheckStatus_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PaymentResult" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/status" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getReservationStatus", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationStatusInfo" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/status" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getReservationStatus_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationStatusInfo" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/payment/{method}/status" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getTransactionStatus", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationPaymentResult" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/payment/{method}/status" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getTransactionStatus_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ReservationPaymentResult" + } + } + } + } + } + } + }, + "/api/v2/public/i18n/languages" : { + "get" : { + "tags" : [ "translations-api-controller" ], + "operationId" : "getSupportedLanguages", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Language" + } + } + } + } + } + } + } + }, + "/api/v2/public/i18n/eu-countries-vat/{lang}" : { + "get" : { + "tags" : [ "translations-api-controller" ], + "operationId" : "getEuCountriesForVat", + "parameters" : [ { + "name" : "lang", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LocalizedCountry" + } + } + } + } + } + } + } + }, + "/api/v2/public/i18n/countries/{lang}" : { + "get" : { + "tags" : [ "translations-api-controller" ], + "operationId" : "getCountries", + "parameters" : [ { + "name" : "lang", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LocalizedCountry" + } + } + } + } + } + } + } + }, + "/api/v2/public/i18n/countries-vat/{lang}" : { + "get" : { + "tags" : [ "translations-api-controller" ], + "operationId" : "getCountriesForVat", + "parameters" : [ { + "name" : "lang", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LocalizedCountry" + } + } + } + } + } + } + } + }, + "/api/v2/public/i18n/bundle/{lang}" : { + "get" : { + "tags" : [ "translations-api-controller" ], + "operationId" : "getPublicTranslations", + "parameters" : [ { + "name" : "lang", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "withSystemOverride", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : true + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/api/v2/public/events" : { + "get" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "listEvents", + "parameters" : [ { + "name" : "searchOptions", + "in" : "query", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/SearchOptions" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BasicEventInfo" + } + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}" : { + "get" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "getEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/EventWithAdditionalInfo" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/validate-code" : { + "get" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "validateCode", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "code", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseEventCode" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/full" : { + "get" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "getTicket", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketsByTicketCategory" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/download-ticket" : { + "get" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "generateTicketPdf", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/code/{checkInCode}/check-in-info" : { + "get" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "getCheckInInfo", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "checkInCode", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "tz", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/OnlineCheckInInfo" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/code.png" : { + "get" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "showQrCode", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/event/{eventName}/ticket/{ticketIdentifier}/code.png" : { + "get" : { + "tags" : [ "ticket-api-v-2-controller" ], + "operationId" : "showQrCode_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/event/{eventName}/ticket-categories" : { + "get" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "getTicketCategories", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "code", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ItemsByCategory" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/receipt" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getReceipt", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/invoice" : { + "get" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "getInvoice", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/event/{eventName}/poll" : { + "get" : { + "tags" : [ "poll-api-controller" ], + "operationId" : "getAll", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pin", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponseListPoll" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/poll/{pollId}" : { + "get" : { + "tags" : [ "poll-api-controller" ], + "operationId" : "getSingle", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, { + "name" : "pin", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ValidatedResponsePollWithOptions" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/code/{code}" : { + "get" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "handleCode", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "code", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/public/event/{eventName}/calendar/{locale}" : { + "get" : { + "tags" : [ "event-api-v-2-controller" ], + "operationId" : "getCalendar", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "locale", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "type", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketId", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v2/info" : { + "get" : { + "tags" : [ "info-api-controller" ], + "operationId" : "getInfo", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/AlfioInfo" + } + } + } + } + } + } + }, + "/api/v2/admin/i18n/bundle/{lang}" : { + "get" : { + "tags" : [ "translations-api-controller" ], + "operationId" : "getAdminTranslations", + "parameters" : [ { + "name" : "lang", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "withSystemOverride", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : true + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/api/v1/admin/system/organization/list" : { + "get" : { + "tags" : [ "organizations-api-v-1-controller" ], + "operationId" : "getAllOrganizations", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Organization" + } + } + } + } + } + } + } + }, + "/api/v1/admin/subscription/{subscriptionId}" : { + "get" : { + "tags" : [ "subscription-api-v-1-controller" ], + "operationId" : "get", + "parameters" : [ { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorWithStatistics" + } + } + } + } + } + } + }, + "/api/v1/admin/event/{slug}/stats" : { + "get" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "stats", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/EventWithAdditionalInfo" + } + } + } + } + } + } + }, + "/api/v1/admin/event/{slug}/check-in-log" : { + "get" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "checkInLog", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CheckInLogEntry" + } + } + } + } + } + } + } + }, + "/api/reservation/{reservationId}/payment/{method}/status" : { + "get" : { + "tags" : [ "payment-api-controller" ], + "operationId" : "getTransactionStatus_2", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PaymentResult" + } + } + } + } + } + } + }, + "/api/events/{eventName}/reservation/{reservationId}/payment/{method}/status" : { + "get" : { + "tags" : [ "payment-api-controller" ], + "operationId" : "getTransactionStatus_3", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "method", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PaymentResult" + } + } + } + } + } + } + }, + "/api/payment/webhook/saferpay/reservation/{reservationId}/success" : { + "get" : { + "tags" : [ "saferpay-payment-webhook-controller" ], + "operationId" : "handleTransactionNotification", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/api/pass/event/{eventName}/v1/version/passes/{ticketUuid}" : { + "get" : { + "tags" : [ "pass-kit-api-controller" ], + "operationId" : "downloadPassForTicket", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketUuid", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK" + } + } + } + }, + "/api/pass/event/{eventName}/v1/version/passes/{passTypeIdentifier}/{serialNumber}" : { + "get" : { + "tags" : [ "pass-kit-api-controller" ], + "operationId" : "getLatestVersion", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "passTypeIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "serialNumber", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "Authorization", + "in" : "header", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK" + } + } + } + }, + "/api/pass/event/{eventName}/v1/devices/*/registrations/*" : { + "get" : { + "tags" : [ "pass-kit-api-controller" ], + "operationId" : "getRegisteredPasses", + "responses" : { + "200" : { + "description" : "OK" + } + } + } + }, + "/api/attendees/{eventKey}/ticket/{UUID}" : { + "get" : { + "tags" : [ "attendee-api-controller" ], + "operationId" : "getTicketDetails", + "parameters" : [ { + "name" : "eventKey", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "UUID", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultTicketWithAdditionalFields" + } + } + } + } + } + } + }, + "/api/attendees/{eventKey}/sponsor-scan/mine" : { + "get" : { + "tags" : [ "attendee-api-controller" ], + "operationId" : "getScannedBadges", + "parameters" : [ { + "name" : "eventKey", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "from", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SponsorAttendeeData" + } + } + } + } + } + } + } + }, + "/admin/api/{purchaseContextType}/{publicIdentifier}/email/{messageId}" : { + "get" : { + "tags" : [ "email-message-api-controller" ], + "operationId" : "loadEmailMessage", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "messageId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/LightweightEmailMessage" + } + } + } + } + } + } + }, + "/admin/api/{purchaseContextType}/{publicIdentifier}/email/" : { + "get" : { + "tags" : [ "email-message-api-controller" ], + "operationId" : "loadEmailMessages", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "page", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "search", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PageAndContentListLightweightEmailMessage" + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll/{pollId}/stats" : { + "get" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "getStatisticsForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PollStatistics" + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll/{pollId}/filter-tickets" : { + "get" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "findAdditionalAttendees", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, { + "name" : "filter", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PollParticipant" + } + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll/{pollId}/allowed" : { + "get" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "getAllowedAttendees", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PollParticipant" + } + } + } + } + } + } + }, + "delete" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "forbidAttendees", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UpdateParticipantsForm" + } + } + }, + "required" : true + }, + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PollParticipant" + } + } + } + } + } + } + } + }, + "/admin/api/utils/short-name/generate" : { + "get" : { + "tags" : [ "utils-api-controller" ], + "operationId" : "generateShortName", + "parameters" : [ { + "name" : "displayName", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/utils/render-commonmark" : { + "get" : { + "tags" : [ "utils-api-controller" ], + "operationId" : "renderCommonmark", + "parameters" : [ { + "name" : "text", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/utils/currencies" : { + "get" : { + "tags" : [ "utils-api-controller" ], + "operationId" : "getCurrencies", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CurrencyDescriptor" + } + } + } + } + } + } + } + }, + "/admin/api/utils/countriesForVat" : { + "get" : { + "tags" : [ "utils-api-controller" ], + "operationId" : "getCountriesForVat_1", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/admin/api/utils/alfio/info" : { + "get" : { + "tags" : [ "utils-api-controller" ], + "operationId" : "getApplicationInfo", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + } + } + } + } + }, + "/admin/api/users" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "getAllUsers", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/UserWithOrganizations" + } + } + } + } + } + } + } + }, + "/admin/api/users/{id}" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "loadUser", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UserModification" + } + } + } + } + } + }, + "delete" : { + "tags" : [ "users-api-controller" ], + "operationId" : "deleteUser", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/users/current" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "loadCurrentUser", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UserModification" + } + } + } + } + } + } + }, + "/admin/api/user/details" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "retrieveDetails", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/admin/api/user-type" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "getLoggedUserType", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/roles" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "getAllRoles", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/RoleDescriptor" + } + } + } + } + } + } + } + }, + "/admin/api/resource/{name}/metadata" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "getMetadata", + "parameters" : [ { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UploadedResource" + } + } + } + } + } + } + }, + "/admin/api/resource/{name}" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "outputContent", + "parameters" : [ { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "resource-controller" ], + "operationId" : "delete_2", + "parameters" : [ { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/resource-organization/{organizationId}" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "findAllForOrganization", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/UploadedResource" + } + } + } + } + } + } + } + }, + "/admin/api/resource-organization/{organizationId}/{name}/metadata" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "getMetadata_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UploadedResource" + } + } + } + } + } + } + }, + "/admin/api/resource-organization/{organizationId}/{name}" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "outputContent_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "resource-controller" ], + "operationId" : "delete_3", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/resource-event/{organizationId}/{eventId}" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "findAllForEvent", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/UploadedResource" + } + } + } + } + } + } + } + }, + "/admin/api/resource-event/{organizationId}/{eventId}/{name}/metadata" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "getMetadata_2", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/UploadedResource" + } + } + } + } + } + } + }, + "/admin/api/resource-event/{organizationId}/{eventId}/{name}" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "outputContent_2", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "resource-controller" ], + "operationId" : "delete_4", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/tickets-with-additional-data" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "ticketsWithAdditionalData", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/ticket/{ticketId}" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "loadTicket", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultTicket" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/ticket/{ticketId}/full-data" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "loadFullTicketData", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "ticketId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/BookingInfoTicket" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/payment-info" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "getPaymentInfo", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultTransactionAndPaymentInfo" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/email-list" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "getEmailList", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultListLightweightMailMessage" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/billing-documents" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "getBillingDocuments", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultListBillingDocument" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/billing-document/{documentId}" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "getBillingDocument", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "documentId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + }, + "delete" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "invalidateBillingDocument", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "documentId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/{reservationId}/audit" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "getAudit", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultListAudit" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/reservations/list" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "findAll_1", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "page", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "search", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "status", + "in" : "query", + "required" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + } + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PageAndContentListTicketReservation" + } + } + } + } + } + } + }, + "/admin/api/reservation/{purchaseContextType}/{publicIdentifier}/reservations/all-status" : { + "get" : { + "tags" : [ "admin-reservation-api-controller" ], + "operationId" : "getAllStatus", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + } + } + } + } + } + } + } + }, + "/admin/api/promo-code/{promoCodeId}/detailed-usage" : { + "get" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "retrieveDetailedUsage", + "parameters" : [ { + "name" : "promoCodeId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "eventShortName", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PromoCodeUsageResult" + } + } + } + } + } + } + } + }, + "/admin/api/promo-code/{promoCodeId}/count-use" : { + "get" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "countPromoCodeUse", + "parameters" : [ { + "name" : "promoCodeId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + }, + "/admin/api/payments/{purchaseContextType}/{publicIdentifier}/list" : { + "get" : { + "tags" : [ "admin-payments-api-controller" ], + "operationId" : "getPaymentsForPurchaseContext", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "page", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "search", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PageAndContentListReservationPaymentDetail" + } + } + } + } + } + } + }, + "/admin/api/payments/{purchaseContextType}/{publicIdentifier}/download" : { + "get" : { + "tags" : [ "admin-payments-api-controller" ], + "operationId" : "exportPayments", + "parameters" : [ { + "name" : "purchaseContextType", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + }, { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "search", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/paymentProxies/{organizationId}" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getPaymentProxies", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PaymentMethodDTO" + } + } + } + } + } + } + } + }, + "/admin/api/overridable-template/{name}/{locale}" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "getTemplate", + "parameters" : [ { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "GOOGLE_ANALYTICS", "CONFIRMATION_EMAIL_FOR_ORGANIZER", "SEND_RESERVED_CODE", "CONFIRMATION_EMAIL", "CONFIRMATION_EMAIL_SUBSCRIPTION", "OFFLINE_RESERVATION_EXPIRED_EMAIL", "CHARGE_ATTEMPT_FAILED_EMAIL", "CHARGE_ATTEMPT_FAILED_EMAIL_FOR_ORGANIZER", "CREDIT_NOTE_ISSUED_EMAIL", "OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER", "OFFLINE_PAYMENT_MATCHES_FOUND", "REMINDER_EMAIL", "REMINDER_TICKET_ADDITIONAL_INFO", "REMINDER_TICKETS_ASSIGNMENT_EMAIL", "TICKET_EMAIL", "TICKET_EMAIL_FOR_ONLINE_EVENT", "TICKET_HAS_CHANGED_OWNER", "TICKET_HAS_BEEN_CANCELLED", "TICKET_HAS_BEEN_CANCELLED_ADMIN", "TICKET_PDF", "RECEIPT_PDF", "INVOICE_PDF", "CREDIT_NOTE_PDF", "SUBSCRIPTION_PDF", "WAITING_QUEUE_JOINED", "WAITING_QUEUE_RESERVATION_EMAIL", "CUSTOM_MESSAGE" ] + } + }, { + "name" : "locale", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/overridable-template/" : { + "get" : { + "tags" : [ "resource-controller" ], + "operationId" : "getOverridableTemplates", + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "GOOGLE_ANALYTICS", "CONFIRMATION_EMAIL_FOR_ORGANIZER", "SEND_RESERVED_CODE", "CONFIRMATION_EMAIL", "CONFIRMATION_EMAIL_SUBSCRIPTION", "OFFLINE_RESERVATION_EXPIRED_EMAIL", "CHARGE_ATTEMPT_FAILED_EMAIL", "CHARGE_ATTEMPT_FAILED_EMAIL_FOR_ORGANIZER", "CREDIT_NOTE_ISSUED_EMAIL", "OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER", "OFFLINE_PAYMENT_MATCHES_FOUND", "REMINDER_EMAIL", "REMINDER_TICKET_ADDITIONAL_INFO", "REMINDER_TICKETS_ASSIGNMENT_EMAIL", "TICKET_EMAIL", "TICKET_EMAIL_FOR_ONLINE_EVENT", "TICKET_HAS_CHANGED_OWNER", "TICKET_HAS_BEEN_CANCELLED", "TICKET_HAS_BEEN_CANCELLED_ADMIN", "TICKET_PDF", "RECEIPT_PDF", "INVOICE_PDF", "CREDIT_NOTE_PDF", "SUBSCRIPTION_PDF", "WAITING_QUEUE_JOINED", "WAITING_QUEUE_RESERVATION_EMAIL", "CUSTOM_MESSAGE" ] + } + } + } + } + } + } + } + }, + "/admin/api/organizations" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "getAllOrganizations_1", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Organization" + } + } + } + } + } + } + } + }, + "/admin/api/organizations/{id}" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "getOrganization", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Organization" + } + } + } + } + } + } + }, + "/admin/api/organization/{organizationId}/subscription/{subscriptionId}/events" : { + "get" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "getLinkedEvents_1", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EventSubscriptionLink" + } + } + } + } + } + } + } + }, + "/admin/api/organization/{organizationId}/subscription/list" : { + "get" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "findAll_2", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubscriptionDescriptorWithStatistics" + } + } + } + } + } + } + } + }, + "/admin/api/organization/{organizationId}/subscription/active" : { + "get" : { + "tags" : [ "subscription-api-controller" ], + "operationId" : "findActive", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubscriptionDescriptor" + } + } + } + } + } + } + } + }, + "/admin/api/organization/{organizationId}/promo-code" : { + "get" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "listPromoCodeInOrganization", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PromoCodeDiscountWithFormattedTimeAndAmount" + } + } + } + } + } + } + } + }, + "/admin/api/location/timezones" : { + "get" : { + "tags" : [ "location-api-controller" ], + "operationId" : "getTimezones", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/admin/api/location/timezone" : { + "get" : { + "tags" : [ "location-api-controller" ], + "operationId" : "getTimezone", + "parameters" : [ { + "name" : "lat", + "in" : "query", + "required" : true, + "schema" : { + "type" : "number", + "format" : "double" + } + }, { + "name" : "lng", + "in" : "query", + "required" : true, + "schema" : { + "type" : "number", + "format" : "double" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/location/static-map-image" : { + "get" : { + "tags" : [ "location-api-controller" ], + "operationId" : "getMapImage", + "parameters" : [ { + "name" : "lat", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "lng", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/location/map-provider-client-api-key" : { + "get" : { + "tags" : [ "location-api-controller" ], + "operationId" : "getGeoInfoProviderAndKeys", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ProviderAndKeys" + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}" : { + "get" : { + "tags" : [ "group-api-controller" ], + "operationId" : "loadAllGroupsForOrganization", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "showAll", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Group" + } + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}/detail/{listId}" : { + "get" : { + "tags" : [ "group-api-controller" ], + "operationId" : "loadDetail", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "listId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/GroupModification" + } + } + } + } + } + } + }, + "/admin/api/group/for/event/{eventName}" : { + "get" : { + "tags" : [ "group-api-controller" ], + "operationId" : "findActiveGroup", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/LinkedGroup" + } + } + } + } + } + } + }, + "/admin/api/group/for/event/{eventName}/category/{categoryId}" : { + "get" : { + "tags" : [ "group-api-controller" ], + "operationId" : "findActiveGroup_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/LinkedGroup" + } + } + } + } + } + } + }, + "/admin/api/group/for/event/{eventName}/all" : { + "get" : { + "tags" : [ "group-api-controller" ], + "operationId" : "findLinked", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LinkedGroup" + } + } + } + } + } + } + } + }, + "/admin/api/extensions/setting/system" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "getParametersFor", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionParameterMetadataAndValue" + } + } + } + } + } + } + } + } + }, + "/admin/api/extensions/setting/organization/{orgId}" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "getParametersFor_1", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionParameterMetadataAndValue" + } + } + } + } + } + } + } + } + }, + "/admin/api/extensions/setting/organization/{orgId}/event/{shortName}" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "getParametersFor_2", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "shortName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionParameterMetadataAndValue" + } + } + } + } + } + } + } + } + }, + "/admin/api/extensions/sample" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "getSample", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ExtensionSupport" + } + } + } + } + } + } + }, + "/admin/api/extensions/log" : { + "get" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "getLog", + "parameters" : [ { + "name" : "path", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "name", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "type", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "enum" : [ "SUCCESS", "ERROR", "INFO", "WARNING" ] + } + }, { + "name" : "page", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32", + "default" : 0 + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PageAndContentListExtensionLog" + } + } + } + } + } + } + }, + "/admin/api/export/reservations" : { + "get" : { + "tags" : [ "export-api-controller" ], + "operationId" : "downloadAllEvents", + "parameters" : [ { + "name" : "from", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "to", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/expired-events" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllExpiredEvents", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EventStatistic" + } + } + } + } + } + } + } + }, + "/admin/api/events" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllEvents_1", + "parameters" : [ { + "name" : "includeOnline", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + }, { + "name" : "Authorization", + "in" : "header", + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "oneOf" : [ { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EventListItem" + } + }, { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EventStatistic" + } + } ] + } + } + } + } + } + } + }, + "/admin/api/events/{name}" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getSingleEvent", + "parameters" : [ { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/EventAndOrganization" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/ticket-sold-statistics" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getTicketsStatistics", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "from", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "to", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/TicketsStatistics" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/sponsor-scan/export" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "downloadSponsorScanExport", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "format", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "default" : "excel" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/pending-payments" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getPendingPayments", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketReservationWithTransaction" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/pending-payments-count" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getPendingPaymentsCount", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/languages" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAvailableLocales", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/invoices/count" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "countBillingDocumentsForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/fields" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllFields", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SerializablePairStringString" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/export" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "downloadAllTicketsCSV", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "format", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "default" : "excel" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/category/{categoryId}/ticket" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getTicketsInCategory", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "page", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "search", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PageAndContentListTicketWithStatistic" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/categories/{categoryId}/sent-codes" : { + "get" : { + "tags" : [ "special-price-api-controller" ], + "operationId" : "loadSentCodes", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SpecialPrice" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/all-documents" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllInvoices", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/all-documents-xls" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllDocumentsXls", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/additional-services/{type}/export" : { + "get" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "exportAdditionalServices", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "type", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "DONATION", "SUPPLEMENT" ] + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/additional-field" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllAdditionalField", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketFieldConfigurationAndAllDescriptions" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/additional-field/{id}/stats" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getStats", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/RestrictedValueStats" + } + } + } + } + } + } + } + }, + "/admin/api/events/{eventId}/promo-code" : { + "get" : { + "tags" : [ "promo-code-discount-api-controller" ], + "operationId" : "listPromoCodeInEvent", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PromoCodeDiscountWithFormattedTimeAndAmount" + } + } + } + } + } + } + } + }, + "/admin/api/events/names-in-organization/{orgId}" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getEventsNameInOrganization", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/admin/api/events/name-by-ids" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getEventNamesByIds", + "parameters" : [ { + "name" : "eventIds", + "in" : "query", + "required" : true, + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + } + } + } + }, + "/admin/api/events/id/{eventId}" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getSingleEventById", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Event" + } + } + } + } + } + } + }, + "/admin/api/events-supported-languages" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getSupportedLanguages_1", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + } + } + } + } + } + } + }, + "/admin/api/events-count" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getEventsCount", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + }, + "/admin/api/events-all-languages" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllLanguages", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + } + } + } + } + } + } + }, + "/admin/api/event/{eventName}/waiting-queue/load" : { + "get" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "loadAllSubscriptions", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WaitingQueueSubscription" + } + } + } + } + } + } + } + }, + "/admin/api/event/{eventName}/waiting-queue/download" : { + "get" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "downloadAllSubscriptions", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "format", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string", + "default" : "excel" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/event/{eventName}/waiting-queue/count" : { + "get" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "countWaitingPeople", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + }, + "/admin/api/event/{eventName}/attendees/import/{requestId}/status" : { + "get" : { + "tags" : [ "attendee-bulk-import-api-controller" ], + "operationId" : "getRequestsStatus", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "requestId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/ResultAdminReservationRequestStats" + } + } + } + } + } + } + }, + "/admin/api/event/{eventId}/additional-services/count" : { + "get" : { + "tags" : [ "additional-service-api-controller" ], + "operationId" : "countUse", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object", + "additionalProperties" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + } + } + }, + "/admin/api/event/additional-field/templates" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "loadTemplates", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DynamicFieldTemplate" + } + } + } + } + } + } + } + }, + "/admin/api/configuration/setting-categories" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "getSettingCategories", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "GENERAL", "RESERVATION_UI", "RESERVATION_EMBED", "PAYMENT", "PAYMENT_STRIPE", "PAYMENT_SAFERPAY", "PAYMENT_PAYPAL", "PAYMENT_OFFLINE", "PAYMENT_MOLLIE", "INVOICE", "INVOICE_EU", "MAIL", "ALFIO_PI", "MAP", "TRANSLATIONS", "PASS_INTEGRATION", "WALLET_INTEGRATION", "WAITING_LIST", "IMPORT_ATTENDEE", "OPENID", "SUBSCRIPTIONS" ] + } + } + } + } + } + } + } + }, + "/admin/api/configuration/platform-mode/status/{organizationId}" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadPlatformModeStatus", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "boolean" + } + } + } + } + } + } + } + }, + "/admin/api/configuration/organizations/{organizationId}/single/{key}" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "getSingleConfigForOrganization", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "key", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/configuration/organizations/{organizationId}/load" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadOrganizationConfiguration", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Configuration" + } + } + } + } + } + } + } + } + }, + "/admin/api/configuration/load" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadConfiguration", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Configuration" + } + } + } + } + } + } + } + } + }, + "/admin/api/configuration/instance-settings" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadInstanceSettings", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/InstanceSettings" + } + } + } + } + } + } + }, + "/admin/api/configuration/events/{eventName}/single/{key}" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "getSingleConfigForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "key", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/configuration/events/{eventId}/load" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadEventConfiguration", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Configuration" + } + } + } + } + } + } + } + } + }, + "/admin/api/configuration/events/{eventId}/categories/{categoryId}/load" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadCategoryConfiguration", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Configuration" + } + } + } + } + } + } + } + } + }, + "/admin/api/configuration/event/{eventId}/matching-invoices" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "getMatchingInvoicesForEvent", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "from", + "in" : "query", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, { + "name" : "to", + "in" : "query", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + } + }, + "/admin/api/configuration/event/{eventId}/invoice-first-date" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "getFirstInvoiceDate", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string", + "format" : "date-time" + } + } + } + } + } + } + }, + "/admin/api/configuration/eu-countries" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "loadEUCountries", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PairStringString" + } + } + } + } + } + } + } + }, + "/admin/api/configuration/basic-configuration-needed" : { + "get" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "isBasicConfigurationNeeded", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventName}/offline-identifiers" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "getOfflineIdentifiers", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "changedSince", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventName}/label-layout" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "getLabelLayoutForEvent", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/LabelLayout" + } + } + } + } + } + } + }, + "/admin/api/check-in/{eventId}/ticket-identifiers" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "findAllIdentifiersForAdminCheckIn", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "changedSince", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{publicIdentifier}/attendees" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "searchAttendees", + "parameters" : [ { + "name" : "publicIdentifier", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "query", + "in" : "query", + "required" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "page", + "in" : "query", + "required" : false, + "schema" : { + "type" : "integer", + "format" : "int32", + "default" : 0 + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/AttendeeSearchResults" + } + } + } + } + } + } + }, + "/admin/api/check-in/event/{eventName}/statistics" : { + "get" : { + "tags" : [ "check-in-api-controller" ], + "operationId" : "getStatistics", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/CheckInStatistics" + } + } + } + } + } + } + }, + "/admin/api/api-keys/organization/{organizationId}/all" : { + "get" : { + "tags" : [ "users-api-controller" ], + "operationId" : "getAllApiKeys", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/active-events" : { + "get" : { + "tags" : [ "event-api-controller" ], + "operationId" : "getAllActiveEvents", + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/EventStatistic" + } + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/remove-code" : { + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "removeCode", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "type", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "SUBSCRIPTION" ] + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/payment/token" : { + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "removeToken", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/payment/token" : { + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "removeToken_1", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/reservation/{reservationId}/payment" : { + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "deletePaymentAttempt", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v2/public/event/{eventName}/reservation/{reservationId}/payment" : { + "delete" : { + "tags" : [ "reservation-api-v-2-controller" ], + "operationId" : "deletePaymentAttempt_1", + "parameters" : [ { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/api/v1/admin/subscription/{subscriptionId}/deactivate" : { + "delete" : { + "tags" : [ "subscription-api-v-1-controller" ], + "operationId" : "deactivate", + "parameters" : [ { + "name" : "subscriptionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "format" : "uuid" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/api/v1/admin/event/{slug}" : { + "delete" : { + "tags" : [ "event-api-v-1-controller" ], + "operationId" : "delete_1", + "parameters" : [ { + "name" : "slug", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/{eventName}/poll/{pollId}/option/{optionId}" : { + "delete" : { + "tags" : [ "poll-admin-api-controller" ], + "operationId" : "removeOption", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "pollId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + }, { + "name" : "optionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/PollModification" + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}/link/{configurationId}" : { + "delete" : { + "tags" : [ "group-api-controller" ], + "operationId" : "unlinkGroup", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "configurationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}/id/{groupId}" : { + "delete" : { + "tags" : [ "group-api-controller" ], + "operationId" : "deactivateGroup", + "parameters" : [ { + "name" : "groupId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/group/for/{organizationId}/id/{groupId}/member/{memberId}" : { + "delete" : { + "tags" : [ "group-api-controller" ], + "operationId" : "deactivateMember", + "parameters" : [ { + "name" : "groupId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "memberId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/extensions/setting/system/{id}" : { + "delete" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "deleteSystemSettingValue", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/extensions/setting/organization/{orgId}/{id}" : { + "delete" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "deleteOrganizationSettingValue", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/extensions/setting/organization/{orgId}/event/{shortName}/{id}" : { + "delete" : { + "tags" : [ "extension-api-controller" ], + "operationId" : "deleteEventSettingValue", + "parameters" : [ { + "name" : "orgId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "shortName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "id", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/events/{eventName}/reservation/{reservationId}/transaction/{transactionId}/discard" : { + "delete" : { + "tags" : [ "event-api-controller" ], + "operationId" : "discardMatchingPayment", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "transactionId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/pending-payments/{reservationId}" : { + "delete" : { + "tags" : [ "event-api-controller" ], + "operationId" : "deletePendingPayment", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "reservationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "credit", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : false + } + }, { + "name" : "notify", + "in" : "query", + "required" : false, + "schema" : { + "type" : "boolean", + "default" : true + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/category/{categoryId}" : { + "delete" : { + "tags" : [ "event-api-controller" ], + "operationId" : "deleteCategory", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + }, + "/admin/api/events/{eventName}/categories/{categoryId}/codes/{codeId}/recipient" : { + "delete" : { + "tags" : [ "special-price-api-controller" ], + "operationId" : "clearRecipientData", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "codeId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/events/{eventId}" : { + "delete" : { + "tags" : [ "event-api-controller" ], + "operationId" : "deleteEvent", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK" + } + } + } + }, + "/admin/api/event/{eventName}/waiting-queue/subscriber/{subscriberId}" : { + "delete" : { + "tags" : [ "admin-waiting-queue-api-controller" ], + "operationId" : "removeSubscriber", + "parameters" : [ { + "name" : "eventName", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "subscriberId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + } + } + } + } + }, + "/admin/api/configuration/organization/{organizationId}/key/{key}" : { + "delete" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "deleteOrganizationLevelKey", + "parameters" : [ { + "name" : "organizationId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "key", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "NOT_RECOGNIZED", "INIT_COMPLETED", "SYSTEM_API_KEY", "SHOW_PROJECT_BANNER", "SUPPORTED_LANGUAGES", "BASE_URL", "GLOBAL_PRIVACY_POLICY", "GLOBAL_TERMS", "ANNOUNCEMENT_BANNER_CONTENT", "MAPS_PROVIDER", "MAPS_CLIENT_API_KEY", "MAPS_HERE_APP_ID", "MAPS_HERE_APP_CODE", "MAPS_HERE_API_KEY", "RECAPTCHA_API_KEY", "RECAPTCHA_SECRET", "ENABLE_CAPTCHA_FOR_LOGIN", "DISPLAY_STATS_IN_EVENT_DETAIL", "DEMO_MODE_ACCOUNT_EXPIRATION_DAYS", "PLATFORM_MODE_ENABLED", "PLATFORM_FEE", "PLATFORM_FIXED_FEE", "PLATFORM_PERCENTAGE_FEE", "PLATFORM_MINIMUM_FEE", "PLATFORM_MAXIMUM_FEE", "PAYMENT_METHODS_BLACKLIST", "STRIPE_CC_ENABLED", "STRIPE_PUBLIC_KEY", "STRIPE_SECRET_KEY", "STRIPE_CONNECT_CLIENT_ID", "STRIPE_CONNECT_CALLBACK", "STRIPE_WEBHOOK_KEY", "STRIPE_WEBHOOK_PAYMENT_KEY", "STRIPE_CONNECTED_ID", "STRIPE_ENABLE_SCA", "SAFERPAY_ENABLED", "SAFERPAY_LIVE_MODE", "SAFERPAY_API_USERNAME", "SAFERPAY_API_PASSWORD", "SAFERPAY_CUSTOMER_ID", "SAFERPAY_TERMINAL_ID", "SPECIAL_PRICE_CODE_LENGTH", "MAX_AMOUNT_OF_TICKETS_BY_RESERVATION", "ASSIGNMENT_REMINDER_START", "ASSIGNMENT_REMINDER_INTERVAL", "OPTIONAL_DATA_REMINDER_ENABLED", "RESERVATION_TIMEOUT", "RESERVATION_MIN_TIMEOUT_AFTER_FAILED_PAYMENT", "NOTIFY_ALL_FAILED_PAYMENT_ATTEMPTS", "DISPLAY_TICKETS_LEFT_INDICATOR", "ENABLE_CAPTCHA_FOR_TICKET_SELECTION", "DISPLAY_EXPIRED_CATEGORIES", "DISPLAY_DISCOUNT_CODE_BOX", "USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL", "ENABLE_CUSTOMER_REFERENCE", "ENABLE_ATTENDEE_AUTOCOMPLETE", "FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION", "SEND_TICKETS_AUTOMATICALLY", "ALLOW_TICKET_DOWNLOAD", "SEND_RESERVATION_EMAIL_IF_NECESSARY", "ENABLE_TICKET_TRANSFER", "ALLOW_FREE_TICKETS_CANCELLATION", "INCLUDE_CHECK_IN_URL_ICAL", "MAILER_TYPE", "MAX_EMAIL_PER_CYCLE", "MAIL_REPLY_TO", "MAIL_SET_ORG_REPLY_TO", "MAIL_SYSTEM_NOTIFICATION_CC", "MAIL_FOOTER", "SMTP_HOST", "SMTP_PORT", "SMTP_PROTOCOL", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_FROM_EMAIL", "SMTP_PROPERTIES", "BANK_TRANSFER_ENABLED", "DEFERRED_BANK_TRANSFER_ENABLED", "SHOW_ONLY_BASIC_INSTRUCTIONS", "DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL", "OFFLINE_PAYMENT_DAYS", "OFFLINE_REMINDER_HOURS", "ENABLE_CAPTCHA_FOR_OFFLINE_PAYMENTS", "BANK_ACCOUNT_NR", "BANK_ACCOUNT_OWNER", "AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT", "PARTIAL_RESERVATION_ID_LENGTH", "REVOLUT_ENABLED", "REVOLUT_MANUAL_REVIEW", "REVOLUT_LIVE_MODE", "REVOLUT_API_KEY", "MAILGUN_KEY", "MAILGUN_DOMAIN", "MAILGUN_FROM", "MAILGUN_EU", "SENDGRID_API_KEY", "SENDGRID_FROM", "MAILJET_APIKEY_PUBLIC", "MAILJET_APIKEY_PRIVATE", "MAILJET_FROM", "GOOGLE_ANALYTICS_KEY", "GOOGLE_ANALYTICS_ANONYMOUS_MODE", "ENABLE_WAITING_QUEUE", "ENABLE_PRE_REGISTRATION", "ENABLE_WAITING_QUEUE_NOTIFICATION", "WAITING_QUEUE_RESERVATION_TIMEOUT", "STOP_WAITING_QUEUE_SUBSCRIPTIONS", "ENABLE_HTML_EMAILS", "MAIL_ATTEMPTS_COUNT", "PAYPAL_ENABLED", "PAYPAL_CLIENT_ID", "PAYPAL_CLIENT_SECRET", "PAYPAL_LIVE_MODE", "PAYPAL_DEMO_MODE_USERNAME", "PAYPAL_DEMO_MODE_PASSWORD", "MOLLIE_CC_ENABLED", "MOLLIE_API_KEY", "MOLLIE_CONNECT_CLIENT_ID", "MOLLIE_CONNECT_REFRESH_TOKEN", "MOLLIE_CONNECT_CLIENT_SECRET", "MOLLIE_CONNECT_CALLBACK", "MOLLIE_CONNECT_PROFILE_ID", "MOLLIE_CONNECT_LIVE_MODE", "ON_SITE_ENABLED", "SEND_TICKETS_AFTER_IMPORT_ATTENDEE", "CREATE_RESERVATION_FOR_EACH_IMPORTED_ATTENDEE", "VAT_NR", "INVOICE_NUMBER_PATTERN", "INVOICE_ADDRESS", "USE_INVOICE_NUMBER_AS_ID", "VAT_NUMBER_IS_REQUIRED", "GENERATE_ONLY_INVOICE", "REUSE_INVOICE_NUMBER_FOR_CREDIT_NOTE", "ENABLE_ITALY_E_INVOICING", "ITALY_E_INVOICING_SEND_PROFORMA", "ENABLE_EU_VAT_DIRECTIVE", "ENABLE_REVERSE_CHARGE_ONLINE", "ENABLE_REVERSE_CHARGE_IN_PERSON", "ENABLE_VIES_VALIDATION", "APPLY_VAT_FOREIGN_BUSINESS", "COUNTRY_OF_BUSINESS", "EU_COUNTRIES_LIST", "EU_VAT_API_ADDRESS", "APPLY_TAX_TO_CATEGORY", "ENABLE_PASS", "PASSBOOK_TYPE_IDENTIFIER", "PASSBOOK_TEAM_IDENTIFIER", "PASSBOOK_KEYSTORE", "PASSBOOK_KEYSTORE_PASSWORD", "PASSBOOK_PRIVATE_KEY_ALIAS", "ENABLE_WALLET", "WALLET_ISSUER_IDENTIFIER", "WALLET_SERVICE_ACCOUNT_KEY", "WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS", "CHECK_IN_STATS", "ALFIO_PI_INTEGRATION_ENABLED", "OFFLINE_CHECKIN_ENABLED", "LABEL_PRINTING_ENABLED", "LABEL_LAYOUT", "CHECK_IN_COLOR_CONFIGURATION", "SECURITY_CSP_REPORT_ENABLED", "SECURITY_CSP_REPORT_URI", "EMBED_ALLOWED_ORIGINS", "EMBED_POST_MESSAGE_ORIGIN", "TRANSLATION_OVERRIDE", "BASE_CUSTOM_CSS", "EVENT_CUSTOM_CSS", "DESCRIPTION_MAXLENGTH", "OPENID_PUBLIC_ENABLED", "OPENID_CONFIGURATION_JSON", "GENERATE_TICKETS_FOR_SUBSCRIPTIONS" ] + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/key/{key}" : { + "delete" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "deleteKey", + "parameters" : [ { + "name" : "key", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/event/{eventId}/key/{key}" : { + "delete" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "deleteEventLevelKey", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "key", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "NOT_RECOGNIZED", "INIT_COMPLETED", "SYSTEM_API_KEY", "SHOW_PROJECT_BANNER", "SUPPORTED_LANGUAGES", "BASE_URL", "GLOBAL_PRIVACY_POLICY", "GLOBAL_TERMS", "ANNOUNCEMENT_BANNER_CONTENT", "MAPS_PROVIDER", "MAPS_CLIENT_API_KEY", "MAPS_HERE_APP_ID", "MAPS_HERE_APP_CODE", "MAPS_HERE_API_KEY", "RECAPTCHA_API_KEY", "RECAPTCHA_SECRET", "ENABLE_CAPTCHA_FOR_LOGIN", "DISPLAY_STATS_IN_EVENT_DETAIL", "DEMO_MODE_ACCOUNT_EXPIRATION_DAYS", "PLATFORM_MODE_ENABLED", "PLATFORM_FEE", "PLATFORM_FIXED_FEE", "PLATFORM_PERCENTAGE_FEE", "PLATFORM_MINIMUM_FEE", "PLATFORM_MAXIMUM_FEE", "PAYMENT_METHODS_BLACKLIST", "STRIPE_CC_ENABLED", "STRIPE_PUBLIC_KEY", "STRIPE_SECRET_KEY", "STRIPE_CONNECT_CLIENT_ID", "STRIPE_CONNECT_CALLBACK", "STRIPE_WEBHOOK_KEY", "STRIPE_WEBHOOK_PAYMENT_KEY", "STRIPE_CONNECTED_ID", "STRIPE_ENABLE_SCA", "SAFERPAY_ENABLED", "SAFERPAY_LIVE_MODE", "SAFERPAY_API_USERNAME", "SAFERPAY_API_PASSWORD", "SAFERPAY_CUSTOMER_ID", "SAFERPAY_TERMINAL_ID", "SPECIAL_PRICE_CODE_LENGTH", "MAX_AMOUNT_OF_TICKETS_BY_RESERVATION", "ASSIGNMENT_REMINDER_START", "ASSIGNMENT_REMINDER_INTERVAL", "OPTIONAL_DATA_REMINDER_ENABLED", "RESERVATION_TIMEOUT", "RESERVATION_MIN_TIMEOUT_AFTER_FAILED_PAYMENT", "NOTIFY_ALL_FAILED_PAYMENT_ATTEMPTS", "DISPLAY_TICKETS_LEFT_INDICATOR", "ENABLE_CAPTCHA_FOR_TICKET_SELECTION", "DISPLAY_EXPIRED_CATEGORIES", "DISPLAY_DISCOUNT_CODE_BOX", "USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL", "ENABLE_CUSTOMER_REFERENCE", "ENABLE_ATTENDEE_AUTOCOMPLETE", "FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION", "SEND_TICKETS_AUTOMATICALLY", "ALLOW_TICKET_DOWNLOAD", "SEND_RESERVATION_EMAIL_IF_NECESSARY", "ENABLE_TICKET_TRANSFER", "ALLOW_FREE_TICKETS_CANCELLATION", "INCLUDE_CHECK_IN_URL_ICAL", "MAILER_TYPE", "MAX_EMAIL_PER_CYCLE", "MAIL_REPLY_TO", "MAIL_SET_ORG_REPLY_TO", "MAIL_SYSTEM_NOTIFICATION_CC", "MAIL_FOOTER", "SMTP_HOST", "SMTP_PORT", "SMTP_PROTOCOL", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_FROM_EMAIL", "SMTP_PROPERTIES", "BANK_TRANSFER_ENABLED", "DEFERRED_BANK_TRANSFER_ENABLED", "SHOW_ONLY_BASIC_INSTRUCTIONS", "DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL", "OFFLINE_PAYMENT_DAYS", "OFFLINE_REMINDER_HOURS", "ENABLE_CAPTCHA_FOR_OFFLINE_PAYMENTS", "BANK_ACCOUNT_NR", "BANK_ACCOUNT_OWNER", "AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT", "PARTIAL_RESERVATION_ID_LENGTH", "REVOLUT_ENABLED", "REVOLUT_MANUAL_REVIEW", "REVOLUT_LIVE_MODE", "REVOLUT_API_KEY", "MAILGUN_KEY", "MAILGUN_DOMAIN", "MAILGUN_FROM", "MAILGUN_EU", "SENDGRID_API_KEY", "SENDGRID_FROM", "MAILJET_APIKEY_PUBLIC", "MAILJET_APIKEY_PRIVATE", "MAILJET_FROM", "GOOGLE_ANALYTICS_KEY", "GOOGLE_ANALYTICS_ANONYMOUS_MODE", "ENABLE_WAITING_QUEUE", "ENABLE_PRE_REGISTRATION", "ENABLE_WAITING_QUEUE_NOTIFICATION", "WAITING_QUEUE_RESERVATION_TIMEOUT", "STOP_WAITING_QUEUE_SUBSCRIPTIONS", "ENABLE_HTML_EMAILS", "MAIL_ATTEMPTS_COUNT", "PAYPAL_ENABLED", "PAYPAL_CLIENT_ID", "PAYPAL_CLIENT_SECRET", "PAYPAL_LIVE_MODE", "PAYPAL_DEMO_MODE_USERNAME", "PAYPAL_DEMO_MODE_PASSWORD", "MOLLIE_CC_ENABLED", "MOLLIE_API_KEY", "MOLLIE_CONNECT_CLIENT_ID", "MOLLIE_CONNECT_REFRESH_TOKEN", "MOLLIE_CONNECT_CLIENT_SECRET", "MOLLIE_CONNECT_CALLBACK", "MOLLIE_CONNECT_PROFILE_ID", "MOLLIE_CONNECT_LIVE_MODE", "ON_SITE_ENABLED", "SEND_TICKETS_AFTER_IMPORT_ATTENDEE", "CREATE_RESERVATION_FOR_EACH_IMPORTED_ATTENDEE", "VAT_NR", "INVOICE_NUMBER_PATTERN", "INVOICE_ADDRESS", "USE_INVOICE_NUMBER_AS_ID", "VAT_NUMBER_IS_REQUIRED", "GENERATE_ONLY_INVOICE", "REUSE_INVOICE_NUMBER_FOR_CREDIT_NOTE", "ENABLE_ITALY_E_INVOICING", "ITALY_E_INVOICING_SEND_PROFORMA", "ENABLE_EU_VAT_DIRECTIVE", "ENABLE_REVERSE_CHARGE_ONLINE", "ENABLE_REVERSE_CHARGE_IN_PERSON", "ENABLE_VIES_VALIDATION", "APPLY_VAT_FOREIGN_BUSINESS", "COUNTRY_OF_BUSINESS", "EU_COUNTRIES_LIST", "EU_VAT_API_ADDRESS", "APPLY_TAX_TO_CATEGORY", "ENABLE_PASS", "PASSBOOK_TYPE_IDENTIFIER", "PASSBOOK_TEAM_IDENTIFIER", "PASSBOOK_KEYSTORE", "PASSBOOK_KEYSTORE_PASSWORD", "PASSBOOK_PRIVATE_KEY_ALIAS", "ENABLE_WALLET", "WALLET_ISSUER_IDENTIFIER", "WALLET_SERVICE_ACCOUNT_KEY", "WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS", "CHECK_IN_STATS", "ALFIO_PI_INTEGRATION_ENABLED", "OFFLINE_CHECKIN_ENABLED", "LABEL_PRINTING_ENABLED", "LABEL_LAYOUT", "CHECK_IN_COLOR_CONFIGURATION", "SECURITY_CSP_REPORT_ENABLED", "SECURITY_CSP_REPORT_URI", "EMBED_ALLOWED_ORIGINS", "EMBED_POST_MESSAGE_ORIGIN", "TRANSLATION_OVERRIDE", "BASE_CUSTOM_CSS", "EVENT_CUSTOM_CSS", "DESCRIPTION_MAXLENGTH", "OPENID_PUBLIC_ENABLED", "OPENID_CONFIGURATION_JSON", "GENERATE_TICKETS_FOR_SUBSCRIPTIONS" ] + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + }, + "/admin/api/configuration/event/{eventId}/category/{categoryId}/key/{key}" : { + "delete" : { + "tags" : [ "configuration-api-controller" ], + "operationId" : "deleteCategoryLevelKey", + "parameters" : [ { + "name" : "eventId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "categoryId", + "in" : "path", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, { + "name" : "key", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "NOT_RECOGNIZED", "INIT_COMPLETED", "SYSTEM_API_KEY", "SHOW_PROJECT_BANNER", "SUPPORTED_LANGUAGES", "BASE_URL", "GLOBAL_PRIVACY_POLICY", "GLOBAL_TERMS", "ANNOUNCEMENT_BANNER_CONTENT", "MAPS_PROVIDER", "MAPS_CLIENT_API_KEY", "MAPS_HERE_APP_ID", "MAPS_HERE_APP_CODE", "MAPS_HERE_API_KEY", "RECAPTCHA_API_KEY", "RECAPTCHA_SECRET", "ENABLE_CAPTCHA_FOR_LOGIN", "DISPLAY_STATS_IN_EVENT_DETAIL", "DEMO_MODE_ACCOUNT_EXPIRATION_DAYS", "PLATFORM_MODE_ENABLED", "PLATFORM_FEE", "PLATFORM_FIXED_FEE", "PLATFORM_PERCENTAGE_FEE", "PLATFORM_MINIMUM_FEE", "PLATFORM_MAXIMUM_FEE", "PAYMENT_METHODS_BLACKLIST", "STRIPE_CC_ENABLED", "STRIPE_PUBLIC_KEY", "STRIPE_SECRET_KEY", "STRIPE_CONNECT_CLIENT_ID", "STRIPE_CONNECT_CALLBACK", "STRIPE_WEBHOOK_KEY", "STRIPE_WEBHOOK_PAYMENT_KEY", "STRIPE_CONNECTED_ID", "STRIPE_ENABLE_SCA", "SAFERPAY_ENABLED", "SAFERPAY_LIVE_MODE", "SAFERPAY_API_USERNAME", "SAFERPAY_API_PASSWORD", "SAFERPAY_CUSTOMER_ID", "SAFERPAY_TERMINAL_ID", "SPECIAL_PRICE_CODE_LENGTH", "MAX_AMOUNT_OF_TICKETS_BY_RESERVATION", "ASSIGNMENT_REMINDER_START", "ASSIGNMENT_REMINDER_INTERVAL", "OPTIONAL_DATA_REMINDER_ENABLED", "RESERVATION_TIMEOUT", "RESERVATION_MIN_TIMEOUT_AFTER_FAILED_PAYMENT", "NOTIFY_ALL_FAILED_PAYMENT_ATTEMPTS", "DISPLAY_TICKETS_LEFT_INDICATOR", "ENABLE_CAPTCHA_FOR_TICKET_SELECTION", "DISPLAY_EXPIRED_CATEGORIES", "DISPLAY_DISCOUNT_CODE_BOX", "USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL", "ENABLE_CUSTOMER_REFERENCE", "ENABLE_ATTENDEE_AUTOCOMPLETE", "FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION", "SEND_TICKETS_AUTOMATICALLY", "ALLOW_TICKET_DOWNLOAD", "SEND_RESERVATION_EMAIL_IF_NECESSARY", "ENABLE_TICKET_TRANSFER", "ALLOW_FREE_TICKETS_CANCELLATION", "INCLUDE_CHECK_IN_URL_ICAL", "MAILER_TYPE", "MAX_EMAIL_PER_CYCLE", "MAIL_REPLY_TO", "MAIL_SET_ORG_REPLY_TO", "MAIL_SYSTEM_NOTIFICATION_CC", "MAIL_FOOTER", "SMTP_HOST", "SMTP_PORT", "SMTP_PROTOCOL", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_FROM_EMAIL", "SMTP_PROPERTIES", "BANK_TRANSFER_ENABLED", "DEFERRED_BANK_TRANSFER_ENABLED", "SHOW_ONLY_BASIC_INSTRUCTIONS", "DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL", "OFFLINE_PAYMENT_DAYS", "OFFLINE_REMINDER_HOURS", "ENABLE_CAPTCHA_FOR_OFFLINE_PAYMENTS", "BANK_ACCOUNT_NR", "BANK_ACCOUNT_OWNER", "AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT", "PARTIAL_RESERVATION_ID_LENGTH", "REVOLUT_ENABLED", "REVOLUT_MANUAL_REVIEW", "REVOLUT_LIVE_MODE", "REVOLUT_API_KEY", "MAILGUN_KEY", "MAILGUN_DOMAIN", "MAILGUN_FROM", "MAILGUN_EU", "SENDGRID_API_KEY", "SENDGRID_FROM", "MAILJET_APIKEY_PUBLIC", "MAILJET_APIKEY_PRIVATE", "MAILJET_FROM", "GOOGLE_ANALYTICS_KEY", "GOOGLE_ANALYTICS_ANONYMOUS_MODE", "ENABLE_WAITING_QUEUE", "ENABLE_PRE_REGISTRATION", "ENABLE_WAITING_QUEUE_NOTIFICATION", "WAITING_QUEUE_RESERVATION_TIMEOUT", "STOP_WAITING_QUEUE_SUBSCRIPTIONS", "ENABLE_HTML_EMAILS", "MAIL_ATTEMPTS_COUNT", "PAYPAL_ENABLED", "PAYPAL_CLIENT_ID", "PAYPAL_CLIENT_SECRET", "PAYPAL_LIVE_MODE", "PAYPAL_DEMO_MODE_USERNAME", "PAYPAL_DEMO_MODE_PASSWORD", "MOLLIE_CC_ENABLED", "MOLLIE_API_KEY", "MOLLIE_CONNECT_CLIENT_ID", "MOLLIE_CONNECT_REFRESH_TOKEN", "MOLLIE_CONNECT_CLIENT_SECRET", "MOLLIE_CONNECT_CALLBACK", "MOLLIE_CONNECT_PROFILE_ID", "MOLLIE_CONNECT_LIVE_MODE", "ON_SITE_ENABLED", "SEND_TICKETS_AFTER_IMPORT_ATTENDEE", "CREATE_RESERVATION_FOR_EACH_IMPORTED_ATTENDEE", "VAT_NR", "INVOICE_NUMBER_PATTERN", "INVOICE_ADDRESS", "USE_INVOICE_NUMBER_AS_ID", "VAT_NUMBER_IS_REQUIRED", "GENERATE_ONLY_INVOICE", "REUSE_INVOICE_NUMBER_FOR_CREDIT_NOTE", "ENABLE_ITALY_E_INVOICING", "ITALY_E_INVOICING_SEND_PROFORMA", "ENABLE_EU_VAT_DIRECTIVE", "ENABLE_REVERSE_CHARGE_ONLINE", "ENABLE_REVERSE_CHARGE_IN_PERSON", "ENABLE_VIES_VALIDATION", "APPLY_VAT_FOREIGN_BUSINESS", "COUNTRY_OF_BUSINESS", "EU_COUNTRIES_LIST", "EU_VAT_API_ADDRESS", "APPLY_TAX_TO_CATEGORY", "ENABLE_PASS", "PASSBOOK_TYPE_IDENTIFIER", "PASSBOOK_TEAM_IDENTIFIER", "PASSBOOK_KEYSTORE", "PASSBOOK_KEYSTORE_PASSWORD", "PASSBOOK_PRIVATE_KEY_ALIAS", "ENABLE_WALLET", "WALLET_ISSUER_IDENTIFIER", "WALLET_SERVICE_ACCOUNT_KEY", "WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS", "CHECK_IN_STATS", "ALFIO_PI_INTEGRATION_ENABLED", "OFFLINE_CHECKIN_ENABLED", "LABEL_PRINTING_ENABLED", "LABEL_LAYOUT", "CHECK_IN_COLOR_CONFIGURATION", "SECURITY_CSP_REPORT_ENABLED", "SECURITY_CSP_REPORT_URI", "EMBED_ALLOWED_ORIGINS", "EMBED_POST_MESSAGE_ORIGIN", "TRANSLATION_OVERRIDE", "BASE_CUSTOM_CSS", "EVENT_CUSTOM_CSS", "DESCRIPTION_MAXLENGTH", "OPENID_PUBLIC_ENABLED", "OPENID_CONFIGURATION_JSON", "GENERATE_TICKETS_FOR_SUBSCRIPTIONS" ] + } + } ], + "responses" : { + "500" : { + "description" : "Internal Server Error", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "405" : { + "description" : "Method Not Allowed", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "*/*" : { + "schema" : { + "type" : "string" + } + } + } + }, + "200" : { + "description" : "OK", + "content" : { + "*/*" : { + "schema" : { + "type" : "boolean" + } + } + } + } + } + } + } + }, + "components" : { + "schemas" : { + "UpdateTicketOwnerForm" : { + "type" : "object", + "properties" : { + "email" : { + "type" : "string" + }, + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "additional" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + } + }, + "ErrorDescriptor" : { + "type" : "object", + "properties" : { + "fieldName" : { + "type" : "string" + }, + "code" : { + "type" : "string" + }, + "arguments" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + }, + "ValidatedResponseBoolean" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "boolean" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "WarningMessage" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + }, + "params" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + }, + "OrganizationApiKey" : { + "type" : "object", + "properties" : { + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "apiKey" : { + "type" : "string" + } + } + }, + "LinkedSubscriptions" : { + "type" : "object", + "properties" : { + "eventSlug" : { + "type" : "string" + }, + "subscriptions" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "uuid" + } + } + } + }, + "UpdatePollStatusForm" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "writeOnly" : true, + "enum" : [ "DRAFT", "OPEN", "CLOSED" ] + } + } + }, + "PollModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "order" : { + "type" : "integer", + "format" : "int32" + }, + "options" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PollOptionModification" + } + }, + "accessRestricted" : { + "type" : "boolean" + }, + "status" : { + "type" : "string", + "enum" : [ "DRAFT", "OPEN", "CLOSED" ] + } + } + }, + "PollOptionModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "UserWithPasswordAndQRCode" : { + "type" : "object", + "properties" : { + "password" : { + "type" : "string" + }, + "uniqueId" : { + "type" : "string" + }, + "qrCode" : { + "type" : "string" + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "INTERNAL", "DEMO", "API_KEY", "PUBLIC" ] + }, + "enabled" : { + "type" : "boolean" + }, + "description" : { + "type" : "string" + }, + "username" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "validToEpochSecond" : { + "type" : "integer", + "format" : "int64" + }, + "validTo" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "ErrorCode" : { + "type" : "object", + "properties" : { + "location" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "arguments" : { + "type" : "array", + "items" : { + "type" : "object" + } + }, + "code" : { + "type" : "string" + } + } + }, + "ResultBoolean" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "type" : "boolean" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "AdminReservationModification" : { + "type" : "object", + "properties" : { + "expiration" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "customerData" : { + "$ref" : "#/components/schemas/CustomerData" + }, + "ticketsInfo" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketsInfo" + } + }, + "language" : { + "type" : "string" + }, + "updateContactData" : { + "type" : "boolean" + }, + "updateAdvancedBillingOptions" : { + "type" : "boolean" + }, + "advancedBillingOptions" : { + "$ref" : "#/components/schemas/AdvancedBillingOptions" + }, + "notification" : { + "$ref" : "#/components/schemas/Notification" + }, + "subscriptionDetails" : { + "$ref" : "#/components/schemas/SubscriptionDetails" + }, + "linkedSubscriptionId" : { + "type" : "string", + "format" : "uuid" + } + } + }, + "AdvancedBillingOptions" : { + "type" : "object", + "properties" : { + "vatApplied" : { + "type" : "boolean" + } + } + }, + "Attendee" : { + "type" : "object", + "properties" : { + "ticketId" : { + "type" : "integer", + "format" : "int32" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "language" : { + "type" : "string" + }, + "forbidReassignment" : { + "type" : "boolean", + "writeOnly" : true + }, + "reference" : { + "type" : "string" + }, + "subscriptionId" : { + "type" : "string", + "format" : "uuid" + }, + "additionalInfo" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "reassignmentForbidden" : { + "type" : "boolean" + }, + "empty" : { + "type" : "boolean" + }, + "fullName" : { + "type" : "string" + } + } + }, + "Category" : { + "type" : "object", + "properties" : { + "existingCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "price" : { + "type" : "number" + }, + "ticketAccessType" : { + "type" : "string", + "enum" : [ "INHERIT", "IN_PERSON", "ONLINE" ] + }, + "existing" : { + "type" : "boolean" + } + } + }, + "CustomerData" : { + "type" : "object", + "properties" : { + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "billingAddress" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "customerReference" : { + "type" : "string" + }, + "vatNr" : { + "type" : "string" + }, + "vatCountryCode" : { + "type" : "string" + }, + "invoicingAdditionalInfo" : { + "$ref" : "#/components/schemas/TicketReservationInvoicingAdditionalInfo" + }, + "fullName" : { + "type" : "string" + } + } + }, + "DateTimeModification" : { + "type" : "object", + "properties" : { + "date" : { + "type" : "string", + "format" : "date" + }, + "time" : { + "$ref" : "#/components/schemas/LocalTime" + } + } + }, + "ItalianEInvoicing" : { + "type" : "object", + "properties" : { + "fiscalCode" : { + "type" : "string" + }, + "referenceType" : { + "type" : "string", + "enum" : [ "ADDRESSEE_CODE", "PEC", "NONE" ] + }, + "addresseeCode" : { + "type" : "string" + }, + "pec" : { + "type" : "string" + }, + "splitPayment" : { + "type" : "boolean" + }, + "reference" : { + "type" : "string" + }, + "empty" : { + "type" : "boolean" + }, + "referenceTypeAsString" : { + "type" : "string" + } + } + }, + "LocalTime" : { + "type" : "object", + "properties" : { + "hour" : { + "type" : "integer", + "format" : "int32" + }, + "minute" : { + "type" : "integer", + "format" : "int32" + }, + "second" : { + "type" : "integer", + "format" : "int32" + }, + "nano" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "Notification" : { + "type" : "object", + "properties" : { + "customer" : { + "type" : "boolean" + }, + "attendees" : { + "type" : "boolean" + } + } + }, + "SubscriptionDetails" : { + "type" : "object", + "properties" : { + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "maxAllowed" : { + "type" : "integer", + "format" : "int32" + }, + "validityFrom" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "validityTo" : { + "$ref" : "#/components/schemas/DateTimeModification" + } + } + }, + "TicketReservationInvoicingAdditionalInfo" : { + "type" : "object", + "properties" : { + "italianEInvoicing" : { + "$ref" : "#/components/schemas/ItalianEInvoicing" + }, + "empty" : { + "type" : "boolean" + } + } + }, + "TicketsInfo" : { + "type" : "object", + "properties" : { + "category" : { + "$ref" : "#/components/schemas/Category" + }, + "attendees" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Attendee" + } + }, + "addSeatsIfNotAvailable" : { + "type" : "boolean" + }, + "updateAttendees" : { + "type" : "boolean" + } + } + }, + "BillingDetails" : { + "type" : "object", + "properties" : { + "companyName" : { + "type" : "string" + }, + "addressLine1" : { + "type" : "string" + }, + "addressLine2" : { + "type" : "string" + }, + "zip" : { + "type" : "string" + }, + "city" : { + "type" : "string" + }, + "state" : { + "type" : "string" + }, + "country" : { + "type" : "string" + }, + "taxId" : { + "type" : "string" + }, + "invoicingAdditionalInfo" : { + "$ref" : "#/components/schemas/TicketReservationInvoicingAdditionalInfo" + }, + "hasTaxId" : { + "type" : "boolean" + } + } + }, + "OrderSummary" : { + "type" : "object", + "properties" : { + "originalTotalPrice" : { + "$ref" : "#/components/schemas/TotalPrice" + }, + "summary" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SummaryRow" + } + }, + "free" : { + "type" : "boolean" + }, + "totalPrice" : { + "type" : "string" + }, + "totalVAT" : { + "type" : "string" + }, + "waitingForPayment" : { + "type" : "boolean" + }, + "deferredPayment" : { + "type" : "boolean" + }, + "cashPayment" : { + "type" : "boolean" + }, + "vatPercentage" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "refundedAmount" : { + "type" : "string" + }, + "vatExempt" : { + "type" : "boolean" + }, + "notYetPaid" : { + "type" : "boolean" + }, + "priceInCents" : { + "type" : "integer", + "format" : "int32" + }, + "ticketAmount" : { + "type" : "integer", + "format" : "int32" + }, + "singleTicketOrder" : { + "type" : "boolean" + }, + "displayVat" : { + "type" : "boolean" + }, + "totalNetPrice" : { + "type" : "string" + }, + "descriptionForPayment" : { + "type" : "string" + }, + "displaySplitPaymentNote" : { + "type" : "boolean" + }, + "priceBeforeTaxes" : { + "type" : "string" + } + } + }, + "PromoCodeDiscount" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "promoCode" : { + "type" : "string" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "utcStart" : { + "type" : "string", + "format" : "date-time" + }, + "utcEnd" : { + "type" : "string", + "format" : "date-time" + }, + "discountAmount" : { + "type" : "integer", + "format" : "int32" + }, + "discountType" : { + "type" : "string", + "enum" : [ "FIXED_AMOUNT", "PERCENTAGE", "FIXED_AMOUNT_RESERVATION", "NONE" ] + }, + "categories" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "maxUsage" : { + "type" : "integer", + "format" : "int32" + }, + "description" : { + "type" : "string" + }, + "emailReference" : { + "type" : "string" + }, + "codeType" : { + "type" : "string", + "enum" : [ "DISCOUNT", "ACCESS", "DYNAMIC" ] + }, + "hiddenCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "currencyCode" : { + "type" : "string" + }, + "dynamic" : { + "type" : "boolean" + }, + "fixedAmount" : { + "type" : "boolean" + } + } + }, + "ResultTicketReservationDescriptor" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "$ref" : "#/components/schemas/TicketReservationDescriptor" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "SerializablePairTicketCategoryListTicket" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Ticket" + } + }, + "key" : { + "$ref" : "#/components/schemas/TicketCategory" + }, + "right" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Ticket" + } + }, + "left" : { + "$ref" : "#/components/schemas/TicketCategory" + } + } + }, + "Subscription" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "format" : "uuid" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "subscriptionDescriptorId" : { + "type" : "string", + "format" : "uuid" + }, + "reservationId" : { + "type" : "string" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "creationTime" : { + "type" : "string", + "format" : "date-time" + }, + "updateTime" : { + "type" : "string", + "format" : "date-time" + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "currency" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "FREE", "PRE_RESERVED", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED" ] + }, + "maxEntries" : { + "type" : "integer", + "format" : "int32" + }, + "validityFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validityTo" : { + "type" : "string", + "format" : "date-time" + }, + "confirmationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "valid" : { + "type" : "boolean" + }, + "pin" : { + "type" : "string" + }, + "formattedValidityTo" : { + "type" : "string" + }, + "formattedValidityFrom" : { + "type" : "string" + } + } + }, + "SubscriptionWithUsageDetails" : { + "type" : "object", + "properties" : { + "subscription" : { + "$ref" : "#/components/schemas/Subscription" + }, + "usageDetails" : { + "$ref" : "#/components/schemas/UsageDetails" + }, + "reservations" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketReservationWithEventIdentifier" + } + } + } + }, + "SummaryRow" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "price" : { + "type" : "string" + }, + "priceBeforeVat" : { + "type" : "string" + }, + "amount" : { + "type" : "integer", + "format" : "int32" + }, + "subTotal" : { + "type" : "string" + }, + "subTotalBeforeVat" : { + "type" : "string" + }, + "originalSubTotal" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "TICKET", "SUBSCRIPTION", "PROMOTION_CODE", "DYNAMIC_DISCOUNT", "ADDITIONAL_SERVICE", "APPLIED_SUBSCRIPTION", "TAX_DETAIL" ] + }, + "taxPercentage" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "discount" : { + "type" : "boolean" + }, + "taxDetail" : { + "type" : "boolean" + }, + "descriptionForPayment" : { + "type" : "string" + } + } + }, + "Ticket" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "uuid" : { + "type" : "string" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "categoryId" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "type" : "string", + "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketsReservationId" : { + "type" : "string" + }, + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "userLanguage" : { + "type" : "string" + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "extReference" : { + "type" : "string" + }, + "currencyCode" : { + "type" : "string" + }, + "tags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "subscriptionId" : { + "type" : "string", + "format" : "uuid" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "assigned" : { + "type" : "boolean" + }, + "checkedIn" : { + "type" : "boolean" + }, + "formattedFinalPrice" : { + "type" : "string" + }, + "formattedNetPrice" : { + "type" : "string" + } + } + }, + "TicketCategory" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "utcInception" : { + "type" : "string", + "format" : "date-time" + }, + "utcExpiration" : { + "type" : "string", + "format" : "date-time" + }, + "maxTickets" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "accessRestricted" : { + "type" : "boolean" + }, + "status" : { + "type" : "string", + "enum" : [ "ACTIVE", "NOT_ACTIVE" ] + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "bounded" : { + "type" : "boolean" + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "code" : { + "type" : "string" + }, + "validCheckInFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validCheckInTo" : { + "type" : "string", + "format" : "date-time" + }, + "ticketValidityStart" : { + "type" : "string", + "format" : "date-time" + }, + "ticketValidityEnd" : { + "type" : "string", + "format" : "date-time" + }, + "currencyCode" : { + "type" : "string" + }, + "ordinal" : { + "type" : "integer", + "format" : "int32" + }, + "ticketCheckInStrategy" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "ONCE_PER_DAY" ] + }, + "ticketAccessType" : { + "type" : "string", + "enum" : [ "INHERIT", "IN_PERSON", "ONLINE" ] + }, + "price" : { + "type" : "number" + }, + "free" : { + "type" : "boolean" + } + } + }, + "TicketReservation" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "validity" : { + "type" : "string", + "format" : "date-time" + }, + "status" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + }, + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "billingAddress" : { + "type" : "string" + }, + "confirmationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "latestReminder" : { + "type" : "string", + "format" : "date-time" + }, + "paymentMethod" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "reminderSent" : { + "type" : "boolean" + }, + "promoCodeDiscountId" : { + "type" : "integer", + "format" : "int32" + }, + "automatic" : { + "type" : "boolean" + }, + "userLanguage" : { + "type" : "string" + }, + "directAssignmentRequested" : { + "type" : "boolean" + }, + "invoiceNumber" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "vatNr" : { + "type" : "string" + }, + "vatCountryCode" : { + "type" : "string" + }, + "invoiceRequested" : { + "type" : "boolean" + }, + "usedVatPercent" : { + "type" : "number" + }, + "vatIncluded" : { + "type" : "boolean" + }, + "creationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "customerReference" : { + "type" : "string" + }, + "registrationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "currencyCode" : { + "type" : "string" + }, + "cancelled" : { + "type" : "boolean" + }, + "hasInvoiceNumber" : { + "type" : "boolean" + }, + "pendingOfflinePayment" : { + "type" : "boolean" + }, + "paidAmount" : { + "type" : "string" + }, + "lineSplittedBillingAddress" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "hasInvoiceOrReceiptDocument" : { + "type" : "boolean" + }, + "hasBeenPaid" : { + "type" : "boolean" + }, + "stuck" : { + "type" : "boolean" + }, + "hasBillingAddress" : { + "type" : "boolean" + }, + "hasVatNumber" : { + "type" : "boolean" + }, + "finalPrice" : { + "type" : "number" + }, + "netPrice" : { + "type" : "number" + }, + "taxablePrice" : { + "type" : "number" + } + } + }, + "TicketReservationAdditionalInfo" : { + "type" : "object", + "properties" : { + "billingAddressCompany" : { + "type" : "string" + }, + "billingAddressLine1" : { + "type" : "string" + }, + "billingAddressLine2" : { + "type" : "string" + }, + "billingAddressZip" : { + "type" : "string" + }, + "billingAddressCity" : { + "type" : "string" + }, + "billingAddressState" : { + "type" : "string" + }, + "billingAddressCountry" : { + "type" : "string" + }, + "vatNr" : { + "type" : "string" + }, + "validated" : { + "type" : "boolean" + }, + "skipVatNr" : { + "type" : "boolean" + }, + "addCompanyBillingDetails" : { + "type" : "boolean" + }, + "invoicingAdditionalInfo" : { + "$ref" : "#/components/schemas/TicketReservationInvoicingAdditionalInfo" + }, + "billingDetails" : { + "$ref" : "#/components/schemas/BillingDetails" + } + } + }, + "TicketReservationDescriptor" : { + "type" : "object", + "properties" : { + "reservation" : { + "$ref" : "#/components/schemas/TicketReservation" + }, + "additionalInfo" : { + "$ref" : "#/components/schemas/TicketReservationAdditionalInfo" + }, + "orderSummary" : { + "$ref" : "#/components/schemas/OrderSummary" + }, + "ticketsByCategory" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SerializablePairTicketCategoryListTicket" + } + }, + "subscriptionDetails" : { + "$ref" : "#/components/schemas/SubscriptionWithUsageDetails" + } + } + }, + "TicketReservationWithEventIdentifier" : { + "type" : "object", + "properties" : { + "eventPublicIdentifier" : { + "type" : "string" + }, + "id" : { + "type" : "string" + }, + "automatic" : { + "type" : "boolean" + }, + "fullName" : { + "type" : "string" + }, + "cancelled" : { + "type" : "boolean" + }, + "currencyCode" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + }, + "promoCodeDiscountId" : { + "type" : "integer", + "format" : "int32" + }, + "hasInvoiceNumber" : { + "type" : "boolean" + }, + "invoiceNumber" : { + "type" : "string" + }, + "paymentMethod" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPrice" : { + "type" : "number" + }, + "vat" : { + "type" : "number" + }, + "appliedDiscount" : { + "type" : "number" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatNr" : { + "type" : "string" + }, + "vatCountryCode" : { + "type" : "string" + }, + "invoiceRequested" : { + "type" : "boolean" + }, + "vatPercentageOrZero" : { + "type" : "number" + }, + "netPrice" : { + "type" : "number" + }, + "email" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "pendingOfflinePayment" : { + "type" : "boolean" + }, + "validity" : { + "type" : "string", + "format" : "date-time" + }, + "billingAddress" : { + "type" : "string" + }, + "customerReference" : { + "type" : "string" + }, + "discount" : { + "$ref" : "#/components/schemas/PromoCodeDiscount" + }, + "registrationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "confirmationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "paidAmount" : { + "type" : "string" + }, + "lineSplittedBillingAddress" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "vatIncluded" : { + "type" : "boolean" + }, + "directAssignmentRequested" : { + "type" : "boolean" + }, + "hasInvoiceOrReceiptDocument" : { + "type" : "boolean" + }, + "hasBeenPaid" : { + "type" : "boolean" + }, + "optionalVatPercentage" : { + "type" : "number" + }, + "taxablePrice" : { + "type" : "number" + }, + "stuck" : { + "type" : "boolean" + }, + "reminderSent" : { + "type" : "boolean" + }, + "hasBillingAddress" : { + "type" : "boolean" + }, + "hasVatNumber" : { + "type" : "boolean" + }, + "latestReminder" : { + "type" : "string", + "format" : "date-time" + }, + "invoiceModel" : { + "type" : "string" + }, + "usedVatPercent" : { + "type" : "number" + }, + "creationTimestamp" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "TotalPrice" : { + "type" : "object", + "properties" : { + "priceWithVAT" : { + "type" : "integer", + "format" : "int32" + }, + "vat" : { + "type" : "integer", + "format" : "int32" + }, + "discount" : { + "type" : "integer", + "format" : "int32" + }, + "discountAppliedCount" : { + "type" : "integer", + "format" : "int32" + }, + "currencyCode" : { + "type" : "string" + } + } + }, + "UsageDetails" : { + "type" : "object", + "properties" : { + "total" : { + "type" : "integer", + "format" : "int32" + }, + "used" : { + "type" : "integer", + "format" : "int32" + }, + "available" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "TransactionMetadataModification" : { + "type" : "object", + "properties" : { + "timestamp" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "notes" : { + "type" : "string" + } + } + }, + "CategoryOrdinalModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "ordinal" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "CallLinkModification" : { + "type" : "object", + "properties" : { + "link" : { + "type" : "string" + }, + "validFrom" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "validTo" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "valid" : { + "type" : "boolean" + } + } + }, + "MetadataModification" : { + "type" : "object", + "properties" : { + "callLinks" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CallLinkModification" + } + }, + "requirementDescriptions" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + }, + "writeOnly" : true + }, + "requirementsDescriptions" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "valid" : { + "type" : "boolean" + } + } + }, + "TicketAllocationModification" : { + "type" : "object", + "properties" : { + "srcCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "targetCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "SetStatusForm" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "boolean" + } + } + }, + "AdditionalField" : { + "type" : "object", + "properties" : { + "order" : { + "type" : "integer", + "format" : "int32" + }, + "useDefinedOrder" : { + "type" : "boolean" + }, + "name" : { + "type" : "string" + }, + "type" : { + "type" : "string" + }, + "required" : { + "type" : "boolean" + }, + "readOnly" : { + "type" : "boolean" + }, + "minLength" : { + "type" : "integer", + "format" : "int32" + }, + "maxLength" : { + "type" : "integer", + "format" : "int32" + }, + "restrictedValues" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/RestrictedValue" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/Description" + } + }, + "forAdditionalService" : { + "$ref" : "#/components/schemas/AdditionalService" + }, + "categoryIds" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "linkedAdditionalService" : { + "$ref" : "#/components/schemas/AdditionalService" + }, + "linkedCategoryIds" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "restrictedValuesAsString" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "disabledValuesAsString" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "linkedCategoriesIds" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + }, + "AdditionalService" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "price" : { + "type" : "number" + }, + "fixPrice" : { + "type" : "boolean" + }, + "ordinal" : { + "type" : "integer", + "format" : "int32" + }, + "availableQuantity" : { + "type" : "integer", + "format" : "int32" + }, + "maxQtyPerOrder" : { + "type" : "integer", + "format" : "int32" + }, + "inception" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "expiration" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "vat" : { + "type" : "number" + }, + "vatType" : { + "type" : "string", + "enum" : [ "INHERITED", "NONE", "CUSTOM_INCLUDED", "CUSTOM_EXCLUDED" ] + }, + "additionalServiceFields" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalField" + } + }, + "title" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalServiceText" + } + }, + "description" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalServiceText" + } + }, + "type" : { + "type" : "string", + "enum" : [ "DONATION", "SUPPLEMENT" ] + }, + "supplementPolicy" : { + "type" : "string", + "enum" : [ "MANDATORY_ONE_FOR_TICKET", "OPTIONAL_UNLIMITED_AMOUNT", "OPTIONAL_MAX_AMOUNT_PER_TICKET", "OPTIONAL_MAX_AMOUNT_PER_RESERVATION" ] + }, + "finalPrice" : { + "type" : "number" + }, + "currency" : { + "type" : "string" + } + } + }, + "AdditionalServiceText" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "locale" : { + "type" : "string" + }, + "value" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "TITLE", "DESCRIPTION" ] + } + } + }, + "Description" : { + "type" : "object", + "properties" : { + "label" : { + "type" : "string" + }, + "placeholder" : { + "type" : "string" + }, + "restrictedValues" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "RestrictedValue" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + } + } + }, + "UpdateProfileForm" : { + "type" : "object", + "properties" : { + "email" : { + "type" : "string" + }, + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "billingAddress" : { + "type" : "string" + }, + "customerReference" : { + "type" : "string" + }, + "expressCheckoutRequested" : { + "type" : "boolean" + }, + "postponeAssignment" : { + "type" : "boolean" + }, + "vatCountryCode" : { + "type" : "string" + }, + "vatNr" : { + "type" : "string" + }, + "invoiceRequested" : { + "type" : "boolean" + }, + "tickets" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/UpdateTicketOwnerForm" + } + }, + "billingAddressCompany" : { + "type" : "string" + }, + "billingAddressLine1" : { + "type" : "string" + }, + "billingAddressLine2" : { + "type" : "string" + }, + "billingAddressZip" : { + "type" : "string" + }, + "billingAddressCity" : { + "type" : "string" + }, + "billingAddressState" : { + "type" : "string" + }, + "addCompanyBillingDetails" : { + "type" : "boolean" + }, + "skipVatNr" : { + "type" : "boolean" + }, + "italyEInvoicingFiscalCode" : { + "type" : "string" + }, + "italyEInvoicingReferenceType" : { + "type" : "string", + "enum" : [ "ADDRESSEE_CODE", "PEC", "NONE" ] + }, + "italyEInvoicingReferenceAddresseeCode" : { + "type" : "string" + }, + "italyEInvoicingReferencePEC" : { + "type" : "string" + }, + "italyEInvoicingSplitPayment" : { + "type" : "boolean" + }, + "differentSubscriptionOwner" : { + "type" : "boolean" + }, + "subscriptionOwner" : { + "$ref" : "#/components/schemas/UpdateSubscriptionOwnerForm" + }, + "additionalInfo" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "business" : { + "type" : "boolean" + } + } + }, + "UpdateSubscriptionOwnerForm" : { + "type" : "object", + "properties" : { + "email" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + } + } + }, + "AdditionalInfoWithLabel" : { + "type" : "object", + "properties" : { + "label" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "values" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + }, + "PublicUserProfile" : { + "type" : "object", + "properties" : { + "billingDetails" : { + "$ref" : "#/components/schemas/BillingDetails" + }, + "additionalData" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/AdditionalInfoWithLabel" + } + } + } + }, + "User" : { + "type" : "object", + "properties" : { + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "profile" : { + "$ref" : "#/components/schemas/PublicUserProfile" + }, + "external" : { + "type" : "boolean" + } + } + }, + "ValidatedResponseUser" : { + "type" : "object", + "properties" : { + "value" : { + "$ref" : "#/components/schemas/User" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "ClientRedirect" : { + "type" : "object", + "properties" : { + "targetUrl" : { + "type" : "string" + }, + "empty" : { + "type" : "boolean" + } + } + }, + "ValidatedResponseString" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "ContactAndTicketsForm" : { + "type" : "object", + "properties" : { + "email" : { + "type" : "string" + }, + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "billingAddress" : { + "type" : "string" + }, + "customerReference" : { + "type" : "string" + }, + "expressCheckoutRequested" : { + "type" : "boolean" + }, + "postponeAssignment" : { + "type" : "boolean" + }, + "vatCountryCode" : { + "type" : "string" + }, + "vatNr" : { + "type" : "string" + }, + "invoiceRequested" : { + "type" : "boolean" + }, + "tickets" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/UpdateTicketOwnerForm" + } + }, + "billingAddressCompany" : { + "type" : "string" + }, + "billingAddressLine1" : { + "type" : "string" + }, + "billingAddressLine2" : { + "type" : "string" + }, + "billingAddressZip" : { + "type" : "string" + }, + "billingAddressCity" : { + "type" : "string" + }, + "billingAddressState" : { + "type" : "string" + }, + "addCompanyBillingDetails" : { + "type" : "boolean" + }, + "skipVatNr" : { + "type" : "boolean" + }, + "italyEInvoicingFiscalCode" : { + "type" : "string" + }, + "italyEInvoicingReferenceType" : { + "type" : "string", + "enum" : [ "ADDRESSEE_CODE", "PEC", "NONE" ] + }, + "italyEInvoicingReferenceAddresseeCode" : { + "type" : "string" + }, + "italyEInvoicingReferencePEC" : { + "type" : "string" + }, + "italyEInvoicingSplitPayment" : { + "type" : "boolean" + }, + "differentSubscriptionOwner" : { + "type" : "boolean" + }, + "subscriptionOwner" : { + "$ref" : "#/components/schemas/UpdateSubscriptionOwnerForm" + }, + "business" : { + "type" : "boolean" + } + } + }, + "MultiValueMapStringString" : { + "type" : "object", + "properties" : { + "all" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + }, + "writeOnly" : true + }, + "empty" : { + "type" : "boolean" + } + }, + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, + "TransactionInitializationToken" : { + "type" : "object", + "properties" : { + "errorMessage" : { + "type" : "string" + }, + "clientSecret" : { + "type" : "string" + }, + "reservationStatusChanged" : { + "type" : "boolean" + }, + "paymentMethod" : { + "type" : "string", + "enum" : [ "CREDIT_CARD", "PAYPAL", "IDEAL", "BANK_TRANSFER", "ON_SITE", "NONE", "APPLE_PAY", "BANCONTACT", "ING_HOME_PAY", "BELFIUS", "KBC", "PRZELEWY_24", "ALIPAY", "POSTFINANCE", "TWINT" ] + }, + "paymentProvider" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "token" : { + "type" : "string" + } + } + }, + "ReservationCodeForm" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "amount" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "SUBSCRIPTION" ] + }, + "codeAsUUID" : { + "type" : "string", + "format" : "uuid" + } + } + }, + "PaymentForm" : { + "type" : "object", + "properties" : { + "gatewayToken" : { + "type" : "string" + }, + "paymentProxy" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "selectedPaymentMethod" : { + "type" : "string", + "enum" : [ "CREDIT_CARD", "PAYPAL", "IDEAL", "BANK_TRANSFER", "ON_SITE", "NONE", "APPLE_PAY", "BANCONTACT", "ING_HOME_PAY", "BELFIUS", "KBC", "PRZELEWY_24", "ALIPAY", "POSTFINANCE", "TWINT" ] + }, + "termAndConditionsAccepted" : { + "type" : "boolean" + }, + "privacyPolicyAccepted" : { + "type" : "boolean" + }, + "hmac" : { + "type" : "string" + }, + "captcha" : { + "type" : "string" + } + } + }, + "ReservationPaymentResult" : { + "type" : "object", + "properties" : { + "success" : { + "type" : "boolean" + }, + "redirect" : { + "type" : "boolean" + }, + "redirectUrl" : { + "type" : "string" + }, + "failure" : { + "type" : "boolean" + }, + "gatewayIdOrNull" : { + "type" : "string" + } + } + }, + "ValidatedResponseReservationPaymentResult" : { + "type" : "object", + "properties" : { + "value" : { + "$ref" : "#/components/schemas/ReservationPaymentResult" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "WaitingQueueSubscriptionForm" : { + "type" : "object", + "properties" : { + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "object", + "properties" : { + "language" : { + "type" : "string" + }, + "script" : { + "type" : "string" + }, + "variant" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "country" : { + "type" : "string" + }, + "unicodeLocaleAttributes" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "unicodeLocaleKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "displayLanguage" : { + "type" : "string" + }, + "displayScript" : { + "type" : "string" + }, + "displayCountry" : { + "type" : "string" + }, + "displayVariant" : { + "type" : "string" + }, + "extensionKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "iso3Language" : { + "type" : "string" + }, + "iso3Country" : { + "type" : "string" + } + } + }, + "termAndConditionsAccepted" : { + "type" : "boolean" + }, + "privacyPolicyAccepted" : { + "type" : "boolean" + }, + "selectedCategory" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "AdditionalServiceReservationModification" : { + "type" : "object", + "properties" : { + "additionalServiceId" : { + "type" : "integer", + "format" : "int32" + }, + "amount" : { + "type" : "number" + }, + "quantity" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "AttendeeData" : { + "type" : "object", + "properties" : { + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "ReservationForm" : { + "type" : "object", + "properties" : { + "promoCode" : { + "type" : "string" + }, + "reservation" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketReservationModification" + } + }, + "additionalService" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalServiceReservationModification" + } + }, + "captcha" : { + "type" : "string" + }, + "additionalServices" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalServiceReservationModification" + } + }, + "tickets" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketReservationModification" + } + } + } + }, + "TicketReservationModification" : { + "type" : "object", + "properties" : { + "ticketCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "quantity" : { + "type" : "integer", + "format" : "int32" + }, + "metadata" : { + "type" : "array", + "items" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + }, + "amount" : { + "type" : "integer", + "format" : "int32", + "deprecated" : true + }, + "attendees" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AttendeeData" + } + } + } + }, + "PollVoteForm" : { + "type" : "object", + "properties" : { + "pin" : { + "type" : "string" + }, + "optionId" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "DynamicDiscount" : { + "type" : "object", + "properties" : { + "discount" : { + "type" : "string" + }, + "discountType" : { + "type" : "string", + "enum" : [ "FIXED_AMOUNT", "PERCENTAGE", "FIXED_AMOUNT_RESERVATION", "NONE" ] + }, + "formattedMessage" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "OrganizationModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "externalId" : { + "type" : "string" + }, + "slug" : { + "type" : "string" + } + } + }, + "Organization" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "externalId" : { + "type" : "string" + }, + "slug" : { + "type" : "string" + }, + "nameOpenId" : { + "type" : "string", + "deprecated" : true + } + } + }, + "DescriptionRequest" : { + "type" : "object", + "properties" : { + "lang" : { + "type" : "string" + }, + "body" : { + "type" : "string" + } + } + }, + "SubscriptionDescriptorModificationRequest" : { + "type" : "object", + "properties" : { + "usageType" : { + "type" : "string", + "writeOnly" : true, + "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] + }, + "termType" : { + "type" : "string", + "writeOnly" : true + }, + "term" : { + "$ref" : "#/components/schemas/SubscriptionTerm" + }, + "title" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + }, + "description" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + }, + "maxAvailable" : { + "type" : "integer", + "format" : "int32", + "writeOnly" : true + }, + "onSaleFrom" : { + "type" : "string", + "format" : "date-time", + "writeOnly" : true + }, + "onSaleTo" : { + "type" : "string", + "format" : "date-time", + "writeOnly" : true + }, + "price" : { + "type" : "number", + "writeOnly" : true + }, + "taxPercentage" : { + "type" : "number", + "writeOnly" : true + }, + "taxPolicy" : { + "type" : "string", + "writeOnly" : true, + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "currencyCode" : { + "type" : "string", + "writeOnly" : true + }, + "isPublic" : { + "type" : "boolean", + "writeOnly" : true + }, + "imageUrl" : { + "type" : "string" + }, + "termsAndConditionsUrl" : { + "type" : "string", + "writeOnly" : true + }, + "privacyPolicyUrl" : { + "type" : "string", + "writeOnly" : true + }, + "timezone" : { + "type" : "string", + "writeOnly" : true + }, + "supportsTicketsGeneration" : { + "type" : "boolean", + "writeOnly" : true + }, + "paymentMethods" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + } + } + }, + "SubscriptionTerm" : { + "type" : "object", + "properties" : { + "units" : { + "type" : "integer", + "format" : "int32" + }, + "validityFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validityTo" : { + "type" : "string", + "format" : "date-time" + }, + "timeUnit" : { + "type" : "string", + "enum" : [ "DAYS", "MONTHS", "YEARS" ] + }, + "numEntries" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ReservationConfiguration" : { + "type" : "object", + "properties" : { + "hideContactData" : { + "type" : "boolean" + }, + "hideConfirmationButtons" : { + "type" : "boolean" + }, + "lockEmailEdit" : { + "type" : "boolean" + } + } + }, + "ReservationUser" : { + "type" : "object", + "properties" : { + "username" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "id" : { + "type" : "string" + } + } + }, + "SubscriptionConfiguration" : { + "type" : "object", + "properties" : { + "displayPin" : { + "type" : "boolean" + } + } + }, + "SubscriptionMetadata" : { + "type" : "object", + "properties" : { + "properties" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "configuration" : { + "$ref" : "#/components/schemas/SubscriptionConfiguration" + } + } + }, + "SubscriptionReservationCreationRequest" : { + "type" : "object", + "properties" : { + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "user" : { + "$ref" : "#/components/schemas/ReservationUser" + }, + "language" : { + "type" : "string" + }, + "reservationConfiguration" : { + "$ref" : "#/components/schemas/ReservationConfiguration" + }, + "subscriptionConfiguration" : { + "$ref" : "#/components/schemas/SubscriptionConfiguration" + }, + "metadataOrNull" : { + "$ref" : "#/components/schemas/SubscriptionMetadata" + } + } + }, + "CreationResponse" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "href" : { + "type" : "string" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + } + } + }, + "AttendeesByCategory" : { + "type" : "object", + "properties" : { + "ticketCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "quantity" : { + "type" : "integer", + "format" : "int32" + }, + "attendees" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AttendeeData" + } + }, + "metadata" : { + "type" : "array", + "items" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + } + }, + "TicketReservationCreationRequest" : { + "type" : "object", + "properties" : { + "tickets" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AttendeesByCategory" + } + }, + "additionalServices" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalServiceReservationModification" + } + }, + "configuration" : { + "$ref" : "#/components/schemas/ReservationConfiguration" + }, + "user" : { + "$ref" : "#/components/schemas/ReservationUser" + }, + "promoCode" : { + "type" : "string" + }, + "language" : { + "type" : "string" + }, + "subscriptionId" : { + "type" : "string" + }, + "reservationConfiguration" : { + "$ref" : "#/components/schemas/ReservationConfiguration" + }, + "captcha" : { + "type" : "string" + } + } + }, + "AttendeeAdditionalInfoRequest" : { + "type" : "object", + "properties" : { + "ordinal" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "GENERIC_TEXT", "PHONE_NUMBER", "MULTI_LINE_TEXT", "LIST_BOX", "COUNTRY", "EU_VAT_NR", "CHECKBOX", "RADIO" ] + }, + "required" : { + "type" : "boolean" + }, + "label" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + }, + "placeholder" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + }, + "restrictedValues" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/RestrictedValueRequest" + } + }, + "contentLength" : { + "$ref" : "#/components/schemas/ContentLengthRequest" + } + } + }, + "CategoryRequest" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "description" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + }, + "maxTickets" : { + "type" : "integer", + "format" : "int32" + }, + "accessRestricted" : { + "type" : "boolean" + }, + "price" : { + "type" : "number" + }, + "startSellingDate" : { + "type" : "string", + "format" : "date-time" + }, + "endSellingDate" : { + "type" : "string", + "format" : "date-time" + }, + "accessCode" : { + "type" : "string" + }, + "customValidity" : { + "$ref" : "#/components/schemas/CustomTicketValidityRequest" + }, + "groupLink" : { + "$ref" : "#/components/schemas/GroupLinkRequest" + }, + "ticketAccessType" : { + "type" : "string", + "enum" : [ "INHERIT", "IN_PERSON", "ONLINE" ] + } + } + }, + "ContentLengthRequest" : { + "type" : "object", + "properties" : { + "min" : { + "type" : "integer", + "format" : "int32" + }, + "max" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "CoordinateRequest" : { + "type" : "object", + "properties" : { + "latitude" : { + "type" : "string" + }, + "longitude" : { + "type" : "string" + } + } + }, + "CustomTicketValidityRequest" : { + "type" : "object", + "properties" : { + "checkInFrom" : { + "type" : "string", + "format" : "date-time" + }, + "checkInTo" : { + "type" : "string", + "format" : "date-time" + }, + "validityStart" : { + "type" : "string", + "format" : "date-time" + }, + "validityEnd" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "EventCreationRequest" : { + "type" : "object", + "properties" : { + "title" : { + "type" : "string" + }, + "slug" : { + "type" : "string" + }, + "description" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + }, + "format" : { + "type" : "string", + "enum" : [ "IN_PERSON", "ONLINE", "HYBRID" ] + }, + "location" : { + "$ref" : "#/components/schemas/LocationRequest" + }, + "timezone" : { + "type" : "string" + }, + "startDate" : { + "type" : "string", + "format" : "date-time" + }, + "endDate" : { + "type" : "string", + "format" : "date-time" + }, + "websiteUrl" : { + "type" : "string" + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "imageUrl" : { + "type" : "string" + }, + "tickets" : { + "$ref" : "#/components/schemas/TicketRequest" + }, + "extensionSettings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionSetting" + } + }, + "additionalInfo" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AttendeeAdditionalInfoRequest" + } + } + } + }, + "ExtensionSetting" : { + "type" : "object", + "properties" : { + "extensionId" : { + "type" : "string" + }, + "key" : { + "type" : "string" + }, + "value" : { + "type" : "string" + } + } + }, + "GroupLinkRequest" : { + "type" : "object", + "properties" : { + "groupId" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "ONCE_PER_VALUE", "LIMITED_QUANTITY", "UNLIMITED" ] + }, + "matchType" : { + "type" : "string", + "enum" : [ "FULL", "EMAIL_DOMAIN" ] + }, + "maxAllocation" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "LocationRequest" : { + "type" : "object", + "properties" : { + "fullAddress" : { + "type" : "string" + }, + "coordinate" : { + "$ref" : "#/components/schemas/CoordinateRequest" + } + } + }, + "PromoCodeRequest" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "validFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validTo" : { + "type" : "string", + "format" : "date-time" + }, + "discountType" : { + "type" : "string", + "enum" : [ "FIXED_AMOUNT", "PERCENTAGE", "FIXED_AMOUNT_RESERVATION", "NONE" ] + }, + "discount" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "RestrictedValueRequest" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "descriptions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DescriptionRequest" + } + } + } + }, + "TicketRequest" : { + "type" : "object", + "properties" : { + "freeOfCharge" : { + "type" : "boolean" + }, + "max" : { + "type" : "integer", + "format" : "int32" + }, + "currency" : { + "type" : "string" + }, + "taxPercentage" : { + "type" : "number" + }, + "taxIncludedInPrice" : { + "type" : "boolean" + }, + "paymentMethods" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "categories" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CategoryRequest" + } + }, + "promoCodes" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PromoCodeRequest" + } + } + } + }, + "SponsorScanRequest" : { + "type" : "object", + "properties" : { + "eventName" : { + "type" : "string" + }, + "ticketIdentifier" : { + "type" : "string" + }, + "notes" : { + "type" : "string" + }, + "leadStatus" : { + "type" : "string", + "enum" : [ "COLD", "WARM", "HOT" ] + } + } + }, + "CheckInResult" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "EVENT_NOT_FOUND", "TICKET_NOT_FOUND", "EMPTY_TICKET_CODE", "INVALID_TICKET_CODE", "INVALID_TICKET_STATE", "ALREADY_CHECK_IN", "MUST_PAY", "OK_READY_TO_BE_CHECKED_IN", "SUCCESS", "INVALID_TICKET_CATEGORY_CHECK_IN_DATE", "BADGE_SCAN_ALREADY_DONE", "OK_READY_FOR_BADGE_SCAN", "BADGE_SCAN_SUCCESS", "ERROR" ] + } + } + }, + "TicketAndCheckInResult" : { + "type" : "object", + "properties" : { + "ticket" : { + "$ref" : "#/components/schemas/TicketWithCategory" + }, + "result" : { + "$ref" : "#/components/schemas/CheckInResult" + } + } + }, + "TicketWithCategory" : { + "type" : "object", + "properties" : { + "category" : { + "$ref" : "#/components/schemas/TicketCategory" + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "fullName" : { + "type" : "string" + }, + "currencyCode" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] + }, + "tags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "assigned" : { + "type" : "boolean" + }, + "subscriptionId" : { + "type" : "string", + "format" : "uuid" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "email" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "categoryId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketsReservationId" : { + "type" : "string" + }, + "uuid" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "checkedIn" : { + "type" : "boolean" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "formattedFinalPrice" : { + "type" : "string" + }, + "formattedNetPrice" : { + "type" : "string" + }, + "extReference" : { + "type" : "string" + }, + "categoryName" : { + "type" : "string" + } + } + }, + "UpdateParticipantsForm" : { + "type" : "object", + "properties" : { + "ticketIds" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + }, + "UserModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "role" : { + "type" : "string" + }, + "username" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "INTERNAL", "DEMO", "API_KEY", "PUBLIC" ] + }, + "validTo" : { + "type" : "integer", + "format" : "int64" + }, + "description" : { + "type" : "string" + }, + "validToAsDateTime" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "PasswordModification" : { + "type" : "object", + "properties" : { + "oldPassword" : { + "type" : "string", + "writeOnly" : true + }, + "newPassword" : { + "type" : "string", + "writeOnly" : true + }, + "newPasswordConfirm" : { + "type" : "string", + "writeOnly" : true + } + } + }, + "ValidationResult" : { + "type" : "object", + "properties" : { + "errorDescriptors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "UploadBase64FileModification" : { + "type" : "object", + "properties" : { + "file" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "byte" + } + }, + "fileAsString" : { + "type" : "string" + }, + "type" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "attributes" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "inputStream" : { + "type" : "object" + } + } + }, + "RefundAmount" : { + "type" : "object", + "properties" : { + "amount" : { + "type" : "string" + } + } + }, + "ResultString" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "type" : "string" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "RemoveTicketsModification" : { + "type" : "object", + "properties" : { + "ticketIds" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "refundTo" : { + "type" : "object", + "additionalProperties" : { + "type" : "boolean" + } + }, + "notify" : { + "type" : "boolean" + }, + "issueCreditNote" : { + "type" : "boolean" + } + } + }, + "RemoveResult" : { + "type" : "object", + "properties" : { + "success" : { + "type" : "boolean" + }, + "creditNoteGenerated" : { + "type" : "boolean" + } + } + }, + "ResultRemoveResult" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "$ref" : "#/components/schemas/RemoveResult" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "PromoCodeDiscountModification" : { + "type" : "object", + "properties" : { + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "promoCode" : { + "type" : "string" + }, + "start" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "end" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "discountAmount" : { + "type" : "number" + }, + "currencyCode" : { + "type" : "string" + }, + "discountType" : { + "type" : "string", + "enum" : [ "FIXED_AMOUNT", "PERCENTAGE", "FIXED_AMOUNT_RESERVATION", "NONE" ] + }, + "categories" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "utcOffset" : { + "type" : "integer", + "format" : "int32" + }, + "maxUsage" : { + "type" : "integer", + "format" : "int32" + }, + "description" : { + "type" : "string" + }, + "emailReference" : { + "type" : "string" + }, + "codeType" : { + "type" : "string", + "enum" : [ "DISCOUNT", "ACCESS", "DYNAMIC" ] + }, + "hiddenCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "discountValue" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "SubscriptionDescriptorModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "format" : "uuid" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "maxAvailable" : { + "type" : "integer", + "format" : "int32" + }, + "onSaleFrom" : { + "type" : "string", + "format" : "date-time" + }, + "onSaleTo" : { + "type" : "string", + "format" : "date-time" + }, + "price" : { + "type" : "number" + }, + "vat" : { + "type" : "number" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "currency" : { + "type" : "string" + }, + "isPublic" : { + "type" : "boolean" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "maxEntries" : { + "type" : "integer", + "format" : "int32" + }, + "validityType" : { + "type" : "string", + "enum" : [ "STANDARD", "CUSTOM", "NOT_SET" ] + }, + "validityTimeUnit" : { + "type" : "string", + "enum" : [ "DAYS", "MONTHS", "YEARS" ] + }, + "validityUnits" : { + "type" : "integer", + "format" : "int32" + }, + "validityFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validityTo" : { + "type" : "string", + "format" : "date-time" + }, + "usageType" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "fileBlobId" : { + "type" : "string" + }, + "paymentProxies" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "timeZone" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "rules" : { + "type" : "object", + "properties" : { + "fixedOffset" : { + "type" : "boolean" + }, + "transitions" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "offsetBefore" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "offsetAfter" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "duration" : { + "type" : "object", + "properties" : { + "seconds" : { + "type" : "integer", + "format" : "int64" + }, + "nano" : { + "type" : "integer", + "format" : "int32" + }, + "negative" : { + "type" : "boolean" + }, + "zero" : { + "type" : "boolean" + }, + "units" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "dateBased" : { + "type" : "boolean" + }, + "timeBased" : { + "type" : "boolean" + }, + "durationEstimated" : { + "type" : "boolean" + } + } + } + } + } + }, + "gap" : { + "type" : "boolean" + }, + "dateTimeBefore" : { + "type" : "string", + "format" : "date-time" + }, + "dateTimeAfter" : { + "type" : "string", + "format" : "date-time" + }, + "overlap" : { + "type" : "boolean" + }, + "instant" : { + "type" : "string", + "format" : "date-time" + } + } + } + }, + "transitionRules" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "month" : { + "type" : "string", + "enum" : [ "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" ] + }, + "timeDefinition" : { + "type" : "string", + "enum" : [ "UTC", "WALL", "STANDARD" ] + }, + "standardOffset" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "offsetBefore" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "offsetAfter" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "dayOfWeek" : { + "type" : "string", + "enum" : [ "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" ] + }, + "dayOfMonthIndicator" : { + "type" : "integer", + "format" : "int32" + }, + "localTime" : { + "$ref" : "#/components/schemas/LocalTime" + }, + "midnightEndOfDay" : { + "type" : "boolean" + } + } + } + } + } + } + } + }, + "supportsTicketsGeneration" : { + "type" : "boolean" + }, + "publicIdentifier" : { + "type" : "string" + }, + "priceCts" : { + "type" : "integer", + "format" : "int32" + }, + "validityFromModel" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "validityToModel" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "onSaleFromModel" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "onSaleToModel" : { + "$ref" : "#/components/schemas/DateTimeModification" + } + } + }, + "LinkedGroupModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "groupId" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "ONCE_PER_VALUE", "LIMITED_QUANTITY", "UNLIMITED" ] + }, + "matchType" : { + "type" : "string", + "enum" : [ "FULL", "EMAIL_DOMAIN" ] + }, + "maxAllocation" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "GroupMemberModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "value" : { + "type" : "string" + }, + "description" : { + "type" : "string" + } + } + }, + "GroupModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "items" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/GroupMemberModification" + } + } + } + }, + "Extension" : { + "type" : "object", + "properties" : { + "path" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "script" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + } + } + }, + "SerializablePairBooleanString" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "key" : { + "type" : "boolean" + }, + "right" : { + "type" : "string" + }, + "left" : { + "type" : "boolean" + } + } + }, + "ExtensionMetadataValue" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "value" : { + "type" : "string" + } + } + }, + "AlfioMetadata" : { + "type" : "object", + "properties" : { + "onlineConfiguration" : { + "$ref" : "#/components/schemas/OnlineConfiguration" + }, + "requirementsDescriptions" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "conditionsToBeAccepted" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ConditionsLink" + } + }, + "copiedFrom" : { + "type" : "string" + } + } + }, + "ConditionsLink" : { + "type" : "object", + "properties" : { + "type" : { + "type" : "string", + "enum" : [ "TERMS_OF_PARTICIPATION", "PRIVACY_POLICY", "CUSTOM" ] + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "url" : { + "type" : "string" + } + } + }, + "EventModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "format" : { + "type" : "string", + "enum" : [ "IN_PERSON", "ONLINE", "HYBRID" ] + }, + "websiteUrl" : { + "type" : "string" + }, + "external" : { + "type" : "string", + "writeOnly" : true + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "imageUrl" : { + "type" : "string" + }, + "fileBlobId" : { + "type" : "string" + }, + "shortName" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "location" : { + "type" : "string" + }, + "latitude" : { + "type" : "string" + }, + "longitude" : { + "type" : "string" + }, + "zoneId" : { + "type" : "string" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "begin" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "end" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "regularPrice" : { + "type" : "number" + }, + "currency" : { + "type" : "string" + }, + "availableSeats" : { + "type" : "integer", + "format" : "int32" + }, + "vatPercentage" : { + "type" : "number" + }, + "vatIncluded" : { + "type" : "boolean" + }, + "allowedPaymentProxies" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "ticketCategories" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketCategoryModification" + } + }, + "freeOfCharge" : { + "type" : "boolean" + }, + "geolocation" : { + "$ref" : "#/components/schemas/LocationDescriptor" + }, + "locales" : { + "type" : "integer", + "format" : "int32" + }, + "ticketFields" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalField" + } + }, + "additionalServices" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalService" + } + }, + "metadata" : { + "$ref" : "#/components/schemas/AlfioMetadata" + }, + "linkedSubscriptions" : { + "type" : "array", + "items" : { + "type" : "string", + "format" : "uuid" + } + }, + "externalUrl" : { + "type" : "string" + }, + "locationDescriptor" : { + "$ref" : "#/components/schemas/LocationDescriptor" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "priceInCents" : { + "type" : "integer", + "format" : "int32" + }, + "online" : { + "type" : "boolean" + } + } + }, + "JoinLink" : { + "type" : "object", + "properties" : { + "link" : { + "type" : "string" + }, + "validFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validTo" : { + "type" : "string", + "format" : "date-time" + }, + "linkText" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "LocationDescriptor" : { + "type" : "object", + "properties" : { + "timeZone" : { + "type" : "string" + }, + "latitude" : { + "type" : "string" + }, + "longitude" : { + "type" : "string" + }, + "mapUrl" : { + "type" : "string" + }, + "hasMapUrl" : { + "type" : "boolean" + } + } + }, + "OnlineConfiguration" : { + "type" : "object", + "properties" : { + "callLinks" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/JoinLink" + } + } + } + }, + "TicketCategoryModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "ticketAccessType" : { + "type" : "string", + "enum" : [ "INHERIT", "IN_PERSON", "ONLINE" ] + }, + "maxTickets" : { + "type" : "integer", + "format" : "int32" + }, + "inception" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "expiration" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "price" : { + "type" : "number" + }, + "tokenGenerationRequested" : { + "type" : "boolean" + }, + "dateString" : { + "type" : "string" + }, + "bounded" : { + "type" : "boolean" + }, + "code" : { + "type" : "string" + }, + "validCheckInFrom" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "validCheckInTo" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "ticketValidityStart" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "ticketValidityEnd" : { + "$ref" : "#/components/schemas/DateTimeModification" + }, + "ordinal" : { + "type" : "integer", + "format" : "int32" + }, + "ticketCheckInStrategy" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "ONCE_PER_DAY" ] + }, + "badgeColor" : { + "type" : "string" + }, + "metadata" : { + "$ref" : "#/components/schemas/AlfioMetadata" + } + } + }, + "TripleBooleanStringString" : { + "type" : "object", + "properties" : { + "right" : { + "type" : "string" + }, + "middle" : { + "type" : "string" + }, + "left" : { + "type" : "boolean" + } + } + }, + "MessageModification" : { + "type" : "object", + "properties" : { + "locale" : { + "type" : "object", + "properties" : { + "language" : { + "type" : "string" + }, + "script" : { + "type" : "string" + }, + "variant" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "country" : { + "type" : "string" + }, + "unicodeLocaleAttributes" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "unicodeLocaleKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "displayLanguage" : { + "type" : "string" + }, + "displayScript" : { + "type" : "string" + }, + "displayCountry" : { + "type" : "string" + }, + "displayVariant" : { + "type" : "string" + }, + "extensionKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "iso3Language" : { + "type" : "string" + }, + "iso3Country" : { + "type" : "string" + } + } + }, + "subject" : { + "type" : "string" + }, + "text" : { + "type" : "string" + }, + "subjectExample" : { + "type" : "string" + }, + "textExample" : { + "type" : "string" + }, + "attachTicket" : { + "type" : "boolean" + } + } + }, + "SendCodeModification" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + }, + "assignee" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "language" : { + "type" : "string" + } + } + }, + "TicketFieldDescriptionModification" : { + "type" : "object", + "properties" : { + "ticketFieldConfigurationId" : { + "type" : "integer", + "format" : "int32" + }, + "locale" : { + "type" : "string" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + }, + "UpdateAdditionalField" : { + "type" : "object", + "properties" : { + "type" : { + "type" : "string" + }, + "required" : { + "type" : "boolean" + }, + "readOnly" : { + "type" : "boolean" + }, + "restrictedValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "disabledValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/TicketFieldDescriptionModification" + } + }, + "categoryIds" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "linkedCategoriesIds" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "restrictedValuesAsString" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "disabledValuesAsString" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + }, + "ConfigurationModification" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "key" : { + "type" : "string" + }, + "value" : { + "type" : "string" + } + } + }, + "FullTicketInfo" : { + "type" : "object", + "properties" : { + "ticket" : { + "$ref" : "#/components/schemas/Ticket" + }, + "ticketReservation" : { + "$ref" : "#/components/schemas/TicketReservation" + }, + "billingDetails" : { + "$ref" : "#/components/schemas/BillingDetails" + }, + "ticketCategory" : { + "$ref" : "#/components/schemas/TicketCategory" + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "fullName" : { + "type" : "string" + }, + "currencyCode" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] + }, + "tags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "assigned" : { + "type" : "boolean" + }, + "subscriptionId" : { + "type" : "string", + "format" : "uuid" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "email" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "categoryId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketsReservationId" : { + "type" : "string" + }, + "uuid" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "checkedIn" : { + "type" : "boolean" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "formattedFinalPrice" : { + "type" : "string" + }, + "formattedNetPrice" : { + "type" : "string" + }, + "extReference" : { + "type" : "string" + } + } + }, + "TicketCode" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + } + } + }, + "OnSitePaymentConfirmation" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "boolean" + }, + "message" : { + "type" : "string" + } + } + }, + "TicketIdentifierCode" : { + "type" : "object", + "properties" : { + "identifier" : { + "type" : "string" + }, + "code" : { + "type" : "string" + } + } + }, + "BulkApiKeyCreation" : { + "type" : "object", + "properties" : { + "organizationId" : { + "type" : "integer", + "format" : "int32", + "writeOnly" : true + }, + "role" : { + "type" : "string", + "writeOnly" : true, + "enum" : [ "ADMIN", "OWNER", "SUPERVISOR", "OPERATOR", "SPONSOR", "API_CONSUMER" ] + }, + "descriptions" : { + "type" : "array", + "writeOnly" : true, + "items" : { + "type" : "string" + } + } + } + }, + "PurchaseContextItem" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "type" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "PurchaseContextWithReservations" : { + "type" : "object", + "properties" : { + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "publicIdentifier" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + }, + "formattedStartDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "sameDay" : { + "type" : "boolean" + }, + "reservations" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ReservationHeader" + } + } + } + }, + "ReservationHeader" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + }, + "formattedExpiresOn" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedConfirmedOn" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedCreatedOn" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "invoiceNumber" : { + "type" : "string" + }, + "finalPrice" : { + "type" : "number" + }, + "currencyCode" : { + "type" : "string" + }, + "usedVatPercent" : { + "type" : "number" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "items" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PurchaseContextItem" + } + } + } + }, + "SearchOptions" : { + "type" : "object", + "properties" : { + "subscription" : { + "type" : "string" + }, + "organizer" : { + "type" : "integer", + "format" : "int32" + }, + "organizerSlug" : { + "type" : "string" + }, + "tags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "empty" : { + "type" : "boolean" + }, + "subscriptionCodeUUIDOrNull" : { + "type" : "string", + "format" : "uuid" + } + } + }, + "BasicSubscriptionDescriptorInfo" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "format" : "uuid" + }, + "fileBlobId" : { + "type" : "string" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "salePeriod" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" + }, + "validityType" : { + "type" : "string", + "enum" : [ "STANDARD", "CUSTOM", "NOT_SET" ] + }, + "usageType" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] + }, + "timeZone" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "rules" : { + "type" : "object", + "properties" : { + "fixedOffset" : { + "type" : "boolean" + }, + "transitions" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "offsetBefore" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "offsetAfter" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "duration" : { + "type" : "object", + "properties" : { + "seconds" : { + "type" : "integer", + "format" : "int64" + }, + "nano" : { + "type" : "integer", + "format" : "int32" + }, + "negative" : { + "type" : "boolean" + }, + "zero" : { + "type" : "boolean" + }, + "units" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "dateBased" : { + "type" : "boolean" + }, + "timeBased" : { + "type" : "boolean" + }, + "durationEstimated" : { + "type" : "boolean" + } + } + } + } + } + }, + "gap" : { + "type" : "boolean" + }, + "dateTimeBefore" : { + "type" : "string", + "format" : "date-time" + }, + "dateTimeAfter" : { + "type" : "string", + "format" : "date-time" + }, + "overlap" : { + "type" : "boolean" + }, + "instant" : { + "type" : "string", + "format" : "date-time" + } + } + } + }, + "transitionRules" : { + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "month" : { + "type" : "string", + "enum" : [ "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" ] + }, + "timeDefinition" : { + "type" : "string", + "enum" : [ "UTC", "WALL", "STANDARD" ] + }, + "standardOffset" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "offsetBefore" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "offsetAfter" : { + "type" : "object", + "properties" : { + "totalSeconds" : { + "type" : "integer", + "format" : "int32" + }, + "id" : { + "type" : "string" + } + } + }, + "dayOfWeek" : { + "type" : "string", + "enum" : [ "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY" ] + }, + "dayOfMonthIndicator" : { + "type" : "integer", + "format" : "int32" + }, + "localTime" : { + "$ref" : "#/components/schemas/LocalTime" + }, + "midnightEndOfDay" : { + "type" : "boolean" + } + } + } + } + } + } + } + }, + "validityTimeUnit" : { + "type" : "string", + "enum" : [ "DAYS", "MONTHS", "YEARS" ] + }, + "validityUnits" : { + "type" : "integer", + "format" : "int32" + }, + "maxEntries" : { + "type" : "integer", + "format" : "int32" + }, + "organizationEmail" : { + "type" : "string" + }, + "organizationName" : { + "type" : "string" + }, + "formattedPrice" : { + "type" : "string" + }, + "currency" : { + "type" : "string" + }, + "currencyDescriptor" : { + "$ref" : "#/components/schemas/CurrencyDescriptor" + }, + "vat" : { + "type" : "number" + }, + "vatIncluded" : { + "type" : "boolean" + }, + "formattedOnSaleFrom" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedOnSaleTo" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedValidFrom" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedValidTo" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Language" + } + } + } + }, + "CurrencyDescriptor" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "symbol" : { + "type" : "string" + }, + "fractionDigits" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "DatesWithTimeZoneOffset" : { + "type" : "object", + "properties" : { + "startDateTime" : { + "type" : "integer", + "format" : "int64" + }, + "startTimeZoneOffset" : { + "type" : "integer", + "format" : "int32" + }, + "endDateTime" : { + "type" : "integer", + "format" : "int64" + }, + "endTimeZoneOffset" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "Language" : { + "type" : "object", + "properties" : { + "locale" : { + "type" : "string" + }, + "displayLanguage" : { + "type" : "string" + } + } + }, + "AnalyticsConfiguration" : { + "type" : "object", + "properties" : { + "googleAnalyticsKey" : { + "type" : "string" + }, + "googleAnalyticsScrambledInfo" : { + "type" : "boolean" + }, + "clientId" : { + "type" : "string" + } + } + }, + "AssignmentConfiguration" : { + "type" : "object", + "properties" : { + "forceAssignment" : { + "type" : "boolean" + }, + "enableAttendeeAutocomplete" : { + "type" : "boolean" + }, + "enableTicketTransfer" : { + "type" : "boolean" + } + } + }, + "CaptchaConfiguration" : { + "type" : "object", + "properties" : { + "captchaForTicketSelection" : { + "type" : "boolean" + }, + "captchaForOfflinePaymentAndFree" : { + "type" : "boolean" + }, + "recaptchaApiKey" : { + "type" : "string" + } + } + }, + "EmbeddingConfiguration" : { + "type" : "object", + "properties" : { + "notificationOrigin" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + } + } + }, + "InvoicingConfiguration" : { + "type" : "object", + "properties" : { + "userCanDownloadReceiptOrInvoice" : { + "type" : "boolean" + }, + "euVatCheckingEnabled" : { + "type" : "boolean" + }, + "invoiceAllowed" : { + "type" : "boolean" + }, + "onlyInvoice" : { + "type" : "boolean" + }, + "customerReferenceEnabled" : { + "type" : "boolean" + }, + "enabledItalyEInvoicing" : { + "type" : "boolean" + }, + "vatNumberStrictlyRequired" : { + "type" : "boolean" + } + } + }, + "OfflinePaymentConfiguration" : { + "type" : "object", + "properties" : { + "showOnlyBasicInstructions" : { + "type" : "boolean" + } + } + }, + "SubscriptionDescriptorWithAdditionalInfo" : { + "type" : "object", + "properties" : { + "invoicingConfiguration" : { + "$ref" : "#/components/schemas/InvoicingConfiguration" + }, + "analyticsConfiguration" : { + "$ref" : "#/components/schemas/AnalyticsConfiguration" + }, + "captchaConfiguration" : { + "$ref" : "#/components/schemas/CaptchaConfiguration" + }, + "embeddingConfiguration" : { + "$ref" : "#/components/schemas/EmbeddingConfiguration" + }, + "bankAccount" : { + "type" : "string" + }, + "bankAccountOwner" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "organizationEmail" : { + "type" : "string" + }, + "organizationName" : { + "type" : "string" + }, + "salePeriod" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" + }, + "formattedOnSaleFrom" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedOnSaleTo" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "timeZone" : { + "type" : "string" + }, + "formattedValidFrom" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedValidTo" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "numAvailable" : { + "type" : "integer", + "format" : "int32" + }, + "currency" : { + "type" : "string" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "vat" : { + "type" : "string" + }, + "maxEntries" : { + "type" : "integer", + "format" : "int32" + }, + "vatIncluded" : { + "type" : "boolean" + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "validityUnits" : { + "type" : "integer", + "format" : "int32" + }, + "validityTimeUnit" : { + "type" : "string", + "enum" : [ "DAYS", "MONTHS", "YEARS" ] + }, + "usageType" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] + }, + "free" : { + "type" : "boolean" + }, + "fileBlobId" : { + "type" : "string" + }, + "validityType" : { + "type" : "string", + "enum" : [ "STANDARD", "CUSTOM", "NOT_SET" ] + }, + "formattedPrice" : { + "type" : "string" + }, + "assignmentConfiguration" : { + "$ref" : "#/components/schemas/AssignmentConfiguration" + }, + "offlinePaymentConfiguration" : { + "$ref" : "#/components/schemas/OfflinePaymentConfiguration" + }, + "canApplySubscriptions" : { + "type" : "boolean" + }, + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Language" + } + }, + "currencyDescriptor" : { + "$ref" : "#/components/schemas/CurrencyDescriptor" + } + } + }, + "PaymentResult" : { + "type" : "object", + "properties" : { + "redirectUrl" : { + "type" : "string" + }, + "initialized" : { + "type" : "boolean" + }, + "successful" : { + "type" : "boolean" + }, + "failed" : { + "type" : "boolean" + }, + "redirect" : { + "type" : "boolean" + }, + "gatewayIdOrNull" : { + "type" : "string" + } + } + }, + "ReservationStatusInfo" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + }, + "validatedBookingInformation" : { + "type" : "boolean" + } + } + }, + "BookingInfoTicket" : { + "type" : "object", + "properties" : { + "uuid" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "fullName" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "assigned" : { + "type" : "boolean" + }, + "locked" : { + "type" : "boolean" + }, + "acquired" : { + "type" : "boolean" + }, + "cancellationEnabled" : { + "type" : "boolean" + }, + "sendMailEnabled" : { + "type" : "boolean" + }, + "downloadEnabled" : { + "type" : "boolean" + }, + "formattedOnlineCheckInDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "onlineEventStarted" : { + "type" : "boolean" + }, + "ticketFieldConfigurationBeforeStandard" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalField" + } + }, + "ticketFieldConfigurationAfterStandard" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalField" + } + } + } + }, + "Field" : { + "type" : "object", + "properties" : { + "fieldIndex" : { + "type" : "integer", + "format" : "int32" + }, + "fieldValue" : { + "type" : "string" + } + } + }, + "PaymentProxyWithParameters" : { + "type" : "object", + "properties" : { + "paymentProxy" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "parameters" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + }, + "ReservationInfo" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "shortId" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "validity" : { + "type" : "integer", + "format" : "int64" + }, + "ticketsByCategory" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketsByTicketCategory" + } + }, + "orderSummary" : { + "$ref" : "#/components/schemas/ReservationInfoOrderSummary" + }, + "status" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + }, + "validatedBookingInformation" : { + "type" : "boolean" + }, + "formattedExpirationDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "invoiceNumber" : { + "type" : "string" + }, + "invoiceRequested" : { + "type" : "boolean" + }, + "invoiceOrReceiptDocumentPresent" : { + "type" : "boolean" + }, + "paid" : { + "type" : "boolean" + }, + "tokenAcquired" : { + "type" : "boolean" + }, + "paymentProxy" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "addCompanyBillingDetails" : { + "type" : "boolean" + }, + "customerReference" : { + "type" : "string" + }, + "skipVatNr" : { + "type" : "boolean" + }, + "billingAddress" : { + "type" : "string" + }, + "billingDetails" : { + "$ref" : "#/components/schemas/BillingDetails" + }, + "containsCategoriesLinkedToGroups" : { + "type" : "boolean" + }, + "activePaymentMethods" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/PaymentProxyWithParameters" + } + }, + "subscriptionInfos" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SubscriptionInfo" + } + }, + "metadata" : { + "$ref" : "#/components/schemas/ReservationMetadata" + } + } + }, + "ReservationInfoOrderSummary" : { + "type" : "object", + "properties" : { + "summary" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ReservationInfoOrderSummaryRow" + } + }, + "totalPrice" : { + "type" : "string" + }, + "free" : { + "type" : "boolean" + }, + "displayVat" : { + "type" : "boolean" + }, + "priceInCents" : { + "type" : "integer", + "format" : "int32" + }, + "descriptionForPayment" : { + "type" : "string" + }, + "totalVAT" : { + "type" : "string" + }, + "vatPercentage" : { + "type" : "string" + }, + "notYetPaid" : { + "type" : "boolean" + } + } + }, + "ReservationInfoOrderSummaryRow" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "amount" : { + "type" : "integer", + "format" : "int32" + }, + "price" : { + "type" : "string" + }, + "subTotal" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "TICKET", "SUBSCRIPTION", "PROMOTION_CODE", "DYNAMIC_DISCOUNT", "ADDITIONAL_SERVICE", "APPLIED_SUBSCRIPTION", "TAX_DETAIL" ] + }, + "taxPercentage" : { + "type" : "string" + } + } + }, + "ReservationMetadata" : { + "type" : "object", + "properties" : { + "hideContactData" : { + "type" : "boolean" + }, + "readyForConfirmation" : { + "type" : "boolean" + }, + "finalized" : { + "type" : "boolean" + }, + "hideConfirmationButtons" : { + "type" : "boolean" + }, + "lockEmailEdit" : { + "type" : "boolean" + } + } + }, + "SubscriptionInfo" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "format" : "uuid" + }, + "pin" : { + "type" : "string" + }, + "usageDetails" : { + "$ref" : "#/components/schemas/UsageDetails" + }, + "owner" : { + "$ref" : "#/components/schemas/SubscriptionOwner" + }, + "configuration" : { + "$ref" : "#/components/schemas/SubscriptionConfiguration" + } + } + }, + "SubscriptionOwner" : { + "type" : "object", + "properties" : { + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + } + } + }, + "TicketsByTicketCategory" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "ticketAccessType" : { + "type" : "string", + "enum" : [ "INHERIT", "IN_PERSON", "ONLINE" ] + }, + "tickets" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BookingInfoTicket" + } + } + } + }, + "LocalizedCountry" : { + "type" : "object", + "properties" : { + "isoCode" : { + "type" : "string" + }, + "name" : { + "type" : "string" + } + } + }, + "BasicEventInfo" : { + "type" : "object", + "properties" : { + "shortName" : { + "type" : "string" + }, + "fileBlobId" : { + "type" : "string" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "format" : { + "type" : "string", + "enum" : [ "IN_PERSON", "ONLINE", "HYBRID" ] + }, + "location" : { + "type" : "string" + }, + "timeZone" : { + "type" : "string" + }, + "datesWithOffset" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" + }, + "sameDay" : { + "type" : "boolean" + }, + "formattedBeginDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedBeginTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Language" + } + } + } + }, + "EventWithAdditionalInfo" : { + "type" : "object", + "properties" : { + "mapUrl" : { + "type" : "string" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "bankAccount" : { + "type" : "string" + }, + "bankAccountOwner" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "formattedBeginDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedBeginTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "invoicingConfiguration" : { + "$ref" : "#/components/schemas/InvoicingConfiguration" + }, + "captchaConfiguration" : { + "$ref" : "#/components/schemas/CaptchaConfiguration" + }, + "assignmentConfiguration" : { + "$ref" : "#/components/schemas/AssignmentConfiguration" + }, + "promotionsConfiguration" : { + "$ref" : "#/components/schemas/PromotionsConfiguration" + }, + "analyticsConfiguration" : { + "$ref" : "#/components/schemas/AnalyticsConfiguration" + }, + "offlinePaymentConfiguration" : { + "$ref" : "#/components/schemas/OfflinePaymentConfiguration" + }, + "embeddingConfiguration" : { + "$ref" : "#/components/schemas/EmbeddingConfiguration" + }, + "i18nOverride" : { + "type" : "object", + "additionalProperties" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + }, + "availableTicketsCount" : { + "type" : "integer", + "format" : "int32" + }, + "customCss" : { + "type" : "string" + }, + "canApplySubscriptions" : { + "type" : "boolean" + }, + "location" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "timeZone" : { + "type" : "string" + }, + "format" : { + "type" : "string", + "enum" : [ "IN_PERSON", "ONLINE", "HYBRID" ] + }, + "currency" : { + "type" : "string" + }, + "shortName" : { + "type" : "string" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "vat" : { + "type" : "string" + }, + "vatIncluded" : { + "type" : "boolean" + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "free" : { + "type" : "boolean" + }, + "fileBlobId" : { + "type" : "string" + }, + "websiteUrl" : { + "type" : "string" + }, + "organizationName" : { + "type" : "string" + }, + "organizationEmail" : { + "type" : "string" + }, + "sameDay" : { + "type" : "boolean" + }, + "datesWithOffset" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" + }, + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Language" + } + }, + "currencyDescriptor" : { + "$ref" : "#/components/schemas/CurrencyDescriptor" + } + } + }, + "PromotionsConfiguration" : { + "type" : "object", + "properties" : { + "hasAccessPromotions" : { + "type" : "boolean" + }, + "usePartnerCode" : { + "type" : "boolean" + } + } + }, + "EventCode" : { + "type" : "object", + "properties" : { + "code" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "SPECIAL_PRICE", "DISCOUNT", "ACCESS" ] + }, + "discountType" : { + "type" : "string", + "enum" : [ "FIXED_AMOUNT", "PERCENTAGE", "FIXED_AMOUNT_RESERVATION", "NONE" ] + }, + "discountAmount" : { + "type" : "string" + } + } + }, + "ValidatedResponseEventCode" : { + "type" : "object", + "properties" : { + "value" : { + "$ref" : "#/components/schemas/EventCode" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "TicketInfo" : { + "type" : "object", + "properties" : { + "fullName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "uuid" : { + "type" : "string" + }, + "ticketCategoryName" : { + "type" : "string" + }, + "reservationFullName" : { + "type" : "string" + }, + "reservationId" : { + "type" : "string" + }, + "deskPaymentRequired" : { + "type" : "boolean" + }, + "timeZone" : { + "type" : "string" + }, + "datesWithOffset" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" + }, + "sameDay" : { + "type" : "boolean" + }, + "formattedBeginDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedBeginTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "OnlineCheckInInfo" : { + "type" : "object", + "properties" : { + "formattedBeginDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedBeginTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndDate" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "formattedEndTime" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "timeZone" : { + "type" : "string" + }, + "datesWithOffset" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" + }, + "sameDay" : { + "type" : "boolean" + } + } + }, + "ItemsByCategory" : { + "type" : "object", + "properties" : { + "ticketCategories" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketCategory" + } + }, + "expiredCategories" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketCategory" + } + }, + "additionalServices" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/AdditionalService" + } + }, + "waitingList" : { + "type" : "boolean" + }, + "preSales" : { + "type" : "boolean" + }, + "ticketCategoriesForWaitingList" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketCategoryForWaitingList" + } + } + } + }, + "TicketCategoryForWaitingList" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + } + } + }, + "Poll" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "status" : { + "type" : "string", + "enum" : [ "DRAFT", "OPEN", "CLOSED" ] + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "allowedTags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "order" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ValidatedResponseListPoll" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Poll" + } + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "PollOption" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "pollId" : { + "type" : "integer", + "format" : "int64" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "PollWithOptions" : { + "type" : "object", + "properties" : { + "poll" : { + "$ref" : "#/components/schemas/Poll" + }, + "options" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PollOption" + } + } + } + }, + "ValidatedResponsePollWithOptions" : { + "type" : "object", + "properties" : { + "value" : { + "$ref" : "#/components/schemas/PollWithOptions" + }, + "warnings" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/WarningMessage" + } + }, + "success" : { + "type" : "boolean" + }, + "errorCount" : { + "type" : "integer", + "format" : "int32" + }, + "validationErrors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorDescriptor" + } + } + } + }, + "AlfioInfo" : { + "type" : "object", + "properties" : { + "demoModeEnabled" : { + "type" : "boolean" + }, + "devModeEnabled" : { + "type" : "boolean" + }, + "prodModeEnabled" : { + "type" : "boolean" + }, + "analyticsConfiguration" : { + "$ref" : "#/components/schemas/AnalyticsConfiguration" + }, + "globalPrivacyPolicyUrl" : { + "type" : "string" + }, + "globalTermsUrl" : { + "type" : "string" + }, + "invoicingConfiguration" : { + "$ref" : "#/components/schemas/InvoicingConfiguration" + }, + "announcementBannerContentHTML" : { + "type" : "string" + }, + "walletConfiguration" : { + "$ref" : "#/components/schemas/WalletConfiguration" + } + } + }, + "WalletConfiguration" : { + "type" : "object", + "properties" : { + "isgWalletEnabled" : { + "type" : "boolean" + }, + "passEnabled" : { + "type" : "boolean" + } + } + }, + "ContentLanguage" : { + "type" : "object", + "properties" : { + "locale" : { + "type" : "object", + "properties" : { + "language" : { + "type" : "string" + }, + "script" : { + "type" : "string" + }, + "variant" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "country" : { + "type" : "string" + }, + "unicodeLocaleAttributes" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "unicodeLocaleKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "displayLanguage" : { + "type" : "string" + }, + "displayScript" : { + "type" : "string" + }, + "displayCountry" : { + "type" : "string" + }, + "displayVariant" : { + "type" : "string" + }, + "extensionKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "iso3Language" : { + "type" : "string" + }, + "iso3Country" : { + "type" : "string" + } + } + }, + "value" : { + "type" : "integer", + "format" : "int32" + }, + "language" : { + "type" : "string" + }, + "displayLanguage" : { + "type" : "string" + } + } + }, + "SubscriptionDescriptor" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "format" : "uuid" + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "maxAvailable" : { + "type" : "integer", + "format" : "int32" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "onSaleFrom" : { + "type" : "string", + "format" : "date-time" + }, + "onSaleTo" : { + "type" : "string", + "format" : "date-time" + }, + "price" : { + "type" : "integer", + "format" : "int32" + }, + "vat" : { + "type" : "number" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "currency" : { + "type" : "string" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "maxEntries" : { + "type" : "integer", + "format" : "int32" + }, + "validityType" : { + "type" : "string", + "enum" : [ "STANDARD", "CUSTOM", "NOT_SET" ] + }, + "validityTimeUnit" : { + "type" : "string", + "enum" : [ "DAYS", "MONTHS", "YEARS" ] + }, + "validityUnits" : { + "type" : "integer", + "format" : "int32" + }, + "validityFrom" : { + "type" : "string", + "format" : "date-time" + }, + "validityTo" : { + "type" : "string", + "format" : "date-time" + }, + "usageType" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "fileBlobId" : { + "type" : "string" + }, + "paymentProxies" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "timeZone" : { + "type" : "string" + }, + "supportsTicketsGeneration" : { + "type" : "boolean" + }, + "public" : { + "type" : "boolean" + }, + "displayName" : { + "type" : "string" + }, + "publicIdentifier" : { + "type" : "string" + }, + "allowedPaymentProxies" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + }, + "begin" : { + "type" : "string", + "format" : "date-time" + }, + "freeOfCharge" : { + "type" : "boolean" + }, + "privacyPolicyLinkOrNull" : { + "type" : "string" + }, + "titleAsText" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "descriptionAsText" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "fileBlobIdIsPresent" : { + "type" : "boolean" + }, + "firstContentLanguage" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + } + }, + "SubscriptionDescriptorWithStatistics" : { + "type" : "object", + "properties" : { + "soldCount" : { + "type" : "integer", + "format" : "int32" + }, + "pendingCount" : { + "type" : "integer", + "format" : "int32" + }, + "linkedEventsCount" : { + "type" : "integer", + "format" : "int32" + }, + "reservationsCount" : { + "type" : "integer", + "format" : "int32" + }, + "descriptor" : { + "$ref" : "#/components/schemas/SubscriptionDescriptor" + }, + "unitPrice" : { + "type" : "number" + }, + "availableCount" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "Configuration" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "key" : { + "type" : "string" + }, + "value" : { + "type" : "string" + }, + "configurationPathLevel" : { + "type" : "string", + "enum" : [ "EXTERNAL", "SYSTEM", "ORGANIZATION", "EVENT", "TICKET_CATEGORY" ] + }, + "description" : { + "type" : "string" + }, + "configurationKey" : { + "type" : "string", + "enum" : [ "NOT_RECOGNIZED", "INIT_COMPLETED", "SYSTEM_API_KEY", "SHOW_PROJECT_BANNER", "SUPPORTED_LANGUAGES", "BASE_URL", "GLOBAL_PRIVACY_POLICY", "GLOBAL_TERMS", "ANNOUNCEMENT_BANNER_CONTENT", "MAPS_PROVIDER", "MAPS_CLIENT_API_KEY", "MAPS_HERE_APP_ID", "MAPS_HERE_APP_CODE", "MAPS_HERE_API_KEY", "RECAPTCHA_API_KEY", "RECAPTCHA_SECRET", "ENABLE_CAPTCHA_FOR_LOGIN", "DISPLAY_STATS_IN_EVENT_DETAIL", "DEMO_MODE_ACCOUNT_EXPIRATION_DAYS", "PLATFORM_MODE_ENABLED", "PLATFORM_FEE", "PLATFORM_FIXED_FEE", "PLATFORM_PERCENTAGE_FEE", "PLATFORM_MINIMUM_FEE", "PLATFORM_MAXIMUM_FEE", "PAYMENT_METHODS_BLACKLIST", "STRIPE_CC_ENABLED", "STRIPE_PUBLIC_KEY", "STRIPE_SECRET_KEY", "STRIPE_CONNECT_CLIENT_ID", "STRIPE_CONNECT_CALLBACK", "STRIPE_WEBHOOK_KEY", "STRIPE_WEBHOOK_PAYMENT_KEY", "STRIPE_CONNECTED_ID", "STRIPE_ENABLE_SCA", "SAFERPAY_ENABLED", "SAFERPAY_LIVE_MODE", "SAFERPAY_API_USERNAME", "SAFERPAY_API_PASSWORD", "SAFERPAY_CUSTOMER_ID", "SAFERPAY_TERMINAL_ID", "SPECIAL_PRICE_CODE_LENGTH", "MAX_AMOUNT_OF_TICKETS_BY_RESERVATION", "ASSIGNMENT_REMINDER_START", "ASSIGNMENT_REMINDER_INTERVAL", "OPTIONAL_DATA_REMINDER_ENABLED", "RESERVATION_TIMEOUT", "RESERVATION_MIN_TIMEOUT_AFTER_FAILED_PAYMENT", "NOTIFY_ALL_FAILED_PAYMENT_ATTEMPTS", "DISPLAY_TICKETS_LEFT_INDICATOR", "ENABLE_CAPTCHA_FOR_TICKET_SELECTION", "DISPLAY_EXPIRED_CATEGORIES", "DISPLAY_DISCOUNT_CODE_BOX", "USE_PARTNER_CODE_INSTEAD_OF_PROMOTIONAL", "ENABLE_CUSTOMER_REFERENCE", "ENABLE_ATTENDEE_AUTOCOMPLETE", "FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION", "SEND_TICKETS_AUTOMATICALLY", "ALLOW_TICKET_DOWNLOAD", "SEND_RESERVATION_EMAIL_IF_NECESSARY", "ENABLE_TICKET_TRANSFER", "ALLOW_FREE_TICKETS_CANCELLATION", "INCLUDE_CHECK_IN_URL_ICAL", "MAILER_TYPE", "MAX_EMAIL_PER_CYCLE", "MAIL_REPLY_TO", "MAIL_SET_ORG_REPLY_TO", "MAIL_SYSTEM_NOTIFICATION_CC", "MAIL_FOOTER", "SMTP_HOST", "SMTP_PORT", "SMTP_PROTOCOL", "SMTP_USERNAME", "SMTP_PASSWORD", "SMTP_FROM_EMAIL", "SMTP_PROPERTIES", "BANK_TRANSFER_ENABLED", "DEFERRED_BANK_TRANSFER_ENABLED", "SHOW_ONLY_BASIC_INSTRUCTIONS", "DEFERRED_BANK_TRANSFER_SEND_CONFIRMATION_EMAIL", "OFFLINE_PAYMENT_DAYS", "OFFLINE_REMINDER_HOURS", "ENABLE_CAPTCHA_FOR_OFFLINE_PAYMENTS", "BANK_ACCOUNT_NR", "BANK_ACCOUNT_OWNER", "AUTOMATIC_REMOVAL_EXPIRED_OFFLINE_PAYMENT", "PARTIAL_RESERVATION_ID_LENGTH", "REVOLUT_ENABLED", "REVOLUT_MANUAL_REVIEW", "REVOLUT_LIVE_MODE", "REVOLUT_API_KEY", "MAILGUN_KEY", "MAILGUN_DOMAIN", "MAILGUN_FROM", "MAILGUN_EU", "SENDGRID_API_KEY", "SENDGRID_FROM", "MAILJET_APIKEY_PUBLIC", "MAILJET_APIKEY_PRIVATE", "MAILJET_FROM", "GOOGLE_ANALYTICS_KEY", "GOOGLE_ANALYTICS_ANONYMOUS_MODE", "ENABLE_WAITING_QUEUE", "ENABLE_PRE_REGISTRATION", "ENABLE_WAITING_QUEUE_NOTIFICATION", "WAITING_QUEUE_RESERVATION_TIMEOUT", "STOP_WAITING_QUEUE_SUBSCRIPTIONS", "ENABLE_HTML_EMAILS", "MAIL_ATTEMPTS_COUNT", "PAYPAL_ENABLED", "PAYPAL_CLIENT_ID", "PAYPAL_CLIENT_SECRET", "PAYPAL_LIVE_MODE", "PAYPAL_DEMO_MODE_USERNAME", "PAYPAL_DEMO_MODE_PASSWORD", "MOLLIE_CC_ENABLED", "MOLLIE_API_KEY", "MOLLIE_CONNECT_CLIENT_ID", "MOLLIE_CONNECT_REFRESH_TOKEN", "MOLLIE_CONNECT_CLIENT_SECRET", "MOLLIE_CONNECT_CALLBACK", "MOLLIE_CONNECT_PROFILE_ID", "MOLLIE_CONNECT_LIVE_MODE", "ON_SITE_ENABLED", "SEND_TICKETS_AFTER_IMPORT_ATTENDEE", "CREATE_RESERVATION_FOR_EACH_IMPORTED_ATTENDEE", "VAT_NR", "INVOICE_NUMBER_PATTERN", "INVOICE_ADDRESS", "USE_INVOICE_NUMBER_AS_ID", "VAT_NUMBER_IS_REQUIRED", "GENERATE_ONLY_INVOICE", "REUSE_INVOICE_NUMBER_FOR_CREDIT_NOTE", "ENABLE_ITALY_E_INVOICING", "ITALY_E_INVOICING_SEND_PROFORMA", "ENABLE_EU_VAT_DIRECTIVE", "ENABLE_REVERSE_CHARGE_ONLINE", "ENABLE_REVERSE_CHARGE_IN_PERSON", "ENABLE_VIES_VALIDATION", "APPLY_VAT_FOREIGN_BUSINESS", "COUNTRY_OF_BUSINESS", "EU_COUNTRIES_LIST", "EU_VAT_API_ADDRESS", "APPLY_TAX_TO_CATEGORY", "ENABLE_PASS", "PASSBOOK_TYPE_IDENTIFIER", "PASSBOOK_TEAM_IDENTIFIER", "PASSBOOK_KEYSTORE", "PASSBOOK_KEYSTORE_PASSWORD", "PASSBOOK_PRIVATE_KEY_ALIAS", "ENABLE_WALLET", "WALLET_ISSUER_IDENTIFIER", "WALLET_SERVICE_ACCOUNT_KEY", "WALLET_OVERWRITE_PREVIOUS_CLASSES_AND_EVENTS", "CHECK_IN_STATS", "ALFIO_PI_INTEGRATION_ENABLED", "OFFLINE_CHECKIN_ENABLED", "LABEL_PRINTING_ENABLED", "LABEL_LAYOUT", "CHECK_IN_COLOR_CONFIGURATION", "SECURITY_CSP_REPORT_ENABLED", "SECURITY_CSP_REPORT_URI", "EMBED_ALLOWED_ORIGINS", "EMBED_POST_MESSAGE_ORIGIN", "TRANSLATION_OVERRIDE", "BASE_CUSTOM_CSS", "EVENT_CUSTOM_CSS", "DESCRIPTION_MAXLENGTH", "OPENID_PUBLIC_ENABLED", "OPENID_CONFIGURATION_JSON", "GENERATE_TICKETS_FOR_SUBSCRIPTIONS" ] + }, + "basic" : { + "type" : "boolean" + }, + "internal" : { + "type" : "boolean" + }, + "componentType" : { + "type" : "string", + "enum" : [ "TEXT", "BOOLEAN", "LIST", "TEXTAREA" ] + } + } + }, + "SpecialPrice" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "code" : { + "type" : "string" + }, + "priceInCents" : { + "type" : "integer", + "format" : "int32" + }, + "ticketCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "type" : "string", + "enum" : [ "WAITING", "FREE", "PENDING", "TAKEN", "CANCELLED" ] + }, + "sentTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "recipientName" : { + "type" : "string" + }, + "recipientEmail" : { + "type" : "string" + }, + "accessCodeId" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "TicketWithStatistic" : { + "type" : "object", + "properties" : { + "ticketReservation" : { + "$ref" : "#/components/schemas/TicketReservation" + }, + "promoCodeOrToken" : { + "type" : "string" + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "timestamp" : { + "type" : "string", + "format" : "date-time" + }, + "fullName" : { + "type" : "string" + }, + "currencyCode" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] + }, + "tags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "assigned" : { + "type" : "boolean" + }, + "transaction" : { + "$ref" : "#/components/schemas/Transaction" + }, + "subscriptionId" : { + "type" : "string", + "format" : "uuid" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPrice" : { + "type" : "number" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "email" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "categoryId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketsReservationId" : { + "type" : "string" + }, + "transactionTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "uuid" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "checkedIn" : { + "type" : "boolean" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "formattedFinalPrice" : { + "type" : "string" + }, + "formattedNetPrice" : { + "type" : "string" + }, + "extReference" : { + "type" : "string" + }, + "stuck" : { + "type" : "boolean" + }, + "paid" : { + "type" : "boolean" + }, + "pending" : { + "type" : "boolean" + }, + "netPrice" : { + "type" : "number" + }, + "taxablePrice" : { + "type" : "number" + } + } + }, + "Transaction" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "transactionId" : { + "type" : "string" + }, + "paymentId" : { + "type" : "string" + }, + "reservationId" : { + "type" : "string" + }, + "timestamp" : { + "type" : "string", + "format" : "date-time" + }, + "priceInCents" : { + "type" : "integer", + "format" : "int32" + }, + "currency" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "paymentProxy" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "platformFee" : { + "type" : "integer", + "format" : "int64" + }, + "gatewayFee" : { + "type" : "integer", + "format" : "int64" + }, + "status" : { + "type" : "string", + "enum" : [ "PENDING", "OFFLINE_MATCHING_PAYMENT_FOUND", "OFFLINE_PENDING_REVIEW", "OFFLINE_DISABLE_MATCH", "COMPLETE", "FAILED", "CANCELLED", "INVALID" ] + }, + "metadata" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "complete" : { + "type" : "boolean" + }, + "potentialMatch" : { + "type" : "boolean" + }, + "notes" : { + "type" : "string" + }, + "timestampEditable" : { + "type" : "boolean" + }, + "formattedAmount" : { + "type" : "string" + } + } + }, + "CheckInLogEntry" : { + "type" : "object", + "properties" : { + "ticketId" : { + "type" : "string" + }, + "attendeeData" : { + "$ref" : "#/components/schemas/AttendeeData" + }, + "audit" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ScanAudit" + } + } + } + }, + "ScanAudit" : { + "type" : "object", + "properties" : { + "ticketUuid" : { + "type" : "string" + }, + "scanTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "username" : { + "type" : "string" + }, + "checkInStatus" : { + "type" : "string", + "enum" : [ "EVENT_NOT_FOUND", "TICKET_NOT_FOUND", "EMPTY_TICKET_CODE", "INVALID_TICKET_CODE", "INVALID_TICKET_STATE", "ALREADY_CHECK_IN", "MUST_PAY", "OK_READY_TO_BE_CHECKED_IN", "SUCCESS", "INVALID_TICKET_CATEGORY_CHECK_IN_DATE", "BADGE_SCAN_ALREADY_DONE", "OK_READY_FOR_BADGE_SCAN", "BADGE_SCAN_SUCCESS", "ERROR" ] + }, + "operation" : { + "type" : "string", + "enum" : [ "SCAN", "REVERT" ] + } + } + }, + "ResultTicketWithAdditionalFields" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "$ref" : "#/components/schemas/TicketWithAdditionalFields" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "TicketWithAdditionalFields" : { + "type" : "object", + "properties" : { + "fullName" : { + "type" : "string" + }, + "currencyCode" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] + }, + "tags" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "additionalFields" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "assigned" : { + "type" : "boolean" + }, + "subscriptionId" : { + "type" : "string", + "format" : "uuid" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "email" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "categoryId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketsReservationId" : { + "type" : "string" + }, + "uuid" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "checkedIn" : { + "type" : "boolean" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "formattedFinalPrice" : { + "type" : "string" + }, + "formattedNetPrice" : { + "type" : "string" + }, + "extReference" : { + "type" : "string" + } + } + }, + "SponsorAttendeeData" : { + "type" : "object", + "properties" : { + "ticketId" : { + "type" : "string" + }, + "timestamp" : { + "type" : "string" + }, + "fullName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + } + } + }, + "LightweightEmailMessage" : { + "type" : "object", + "properties" : { + "message" : { + "type" : "string" + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "type" : "string", + "enum" : [ "WAITING", "RETRY", "IN_PROCESS", "SENT", "ERROR" ] + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "attempts" : { + "type" : "integer", + "format" : "int32" + }, + "checksum" : { + "type" : "string" + }, + "recipient" : { + "type" : "string" + }, + "cc" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "subject" : { + "type" : "string" + }, + "htmlMessage" : { + "type" : "string" + }, + "attachments" : { + "type" : "string" + }, + "purchaseContextType" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + }, + "subscriptionDescriptorId" : { + "type" : "string", + "format" : "uuid" + }, + "requestTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "sentTimestamp" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "PageAndContentListLightweightEmailMessage" : { + "type" : "object", + "properties" : { + "left" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LightweightEmailMessage" + } + }, + "right" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "PollStatistics" : { + "type" : "object", + "properties" : { + "totalVotes" : { + "type" : "integer", + "format" : "int32" + }, + "allowedParticipants" : { + "type" : "integer", + "format" : "int32" + }, + "participationPercentage" : { + "type" : "string" + }, + "optionStatistics" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/StatisticDetail" + } + } + } + }, + "StatisticDetail" : { + "type" : "object", + "properties" : { + "votes" : { + "type" : "integer", + "format" : "int32" + }, + "optionId" : { + "type" : "integer", + "format" : "int64" + }, + "percentage" : { + "type" : "string" + } + } + }, + "PollParticipant" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "categoryName" : { + "type" : "string" + } + } + }, + "UserWithOrganizations" : { + "type" : "object", + "properties" : { + "memberOf" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Organization" + } + }, + "roles" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "ADMIN", "OWNER", "SUPERVISOR", "OPERATOR", "SPONSOR", "API_CONSUMER" ] + } + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "INTERNAL", "DEMO", "API_KEY", "PUBLIC" ] + }, + "enabled" : { + "type" : "boolean" + }, + "description" : { + "type" : "string" + }, + "username" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "validToEpochSecond" : { + "type" : "integer", + "format" : "int64" + }, + "validTo" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "RoleDescriptor" : { + "type" : "object", + "properties" : { + "role" : { + "type" : "string" + }, + "target" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : { + "type" : "string" + } + } + }, + "UploadedResource" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "contentType" : { + "type" : "string" + }, + "contentSize" : { + "type" : "integer", + "format" : "int32" + }, + "creationTime" : { + "type" : "string", + "format" : "date-time" + }, + "attributes" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "ResultTicket" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "$ref" : "#/components/schemas/Ticket" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "PaymentInformation" : { + "type" : "object", + "properties" : { + "paidAmount" : { + "type" : "string" + }, + "refundedAmount" : { + "type" : "string" + }, + "fee" : { + "type" : "string" + }, + "platformFee" : { + "type" : "string" + }, + "fullyRefunded" : { + "type" : "boolean" + } + } + }, + "ResultTransactionAndPaymentInfo" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "$ref" : "#/components/schemas/TransactionAndPaymentInfo" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "TransactionAndPaymentInfo" : { + "type" : "object", + "properties" : { + "paymentMethod" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "transaction" : { + "$ref" : "#/components/schemas/Transaction" + }, + "paymentInformation" : { + "$ref" : "#/components/schemas/PaymentInformation" + }, + "supportRefund" : { + "type" : "boolean" + } + } + }, + "LightweightMailMessage" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "subscriptionDescriptorId" : { + "type" : "string", + "format" : "uuid" + }, + "status" : { + "type" : "string", + "enum" : [ "WAITING", "RETRY", "IN_PROCESS", "SENT", "ERROR" ] + }, + "recipient" : { + "type" : "string" + }, + "cc" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "subject" : { + "type" : "string" + }, + "message" : { + "type" : "string" + }, + "htmlMessage" : { + "type" : "string" + }, + "attachments" : { + "type" : "string" + }, + "checksum" : { + "type" : "string" + }, + "requestTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "sentTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "attempts" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "purchaseContextType" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + } + } + }, + "ResultListLightweightMailMessage" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/LightweightMailMessage" + } + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "BillingDocument" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "number" : { + "type" : "string" + }, + "reservationId" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "INVOICE", "RECEIPT", "CREDIT_NOTE" ] + }, + "model" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "generationTimestamp" : { + "type" : "string", + "format" : "date-time" + }, + "status" : { + "type" : "string", + "enum" : [ "VALID", "NOT_VALID" ] + }, + "externalId" : { + "type" : "string" + } + } + }, + "ResultListBillingDocument" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BillingDocument" + } + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "Audit" : { + "type" : "object", + "properties" : { + "reservationId" : { + "type" : "string" + }, + "eventType" : { + "type" : "string", + "enum" : [ "RESERVATION_CREATE", "RESERVATION_COMPLETE", "RESERVATION_OFFLINE_PAYMENT_CONFIRMED", "CANCEL_RESERVATION_EXPIRED", "CANCEL_RESERVATION", "RESET_PAYMENT", "INIT_PAYMENT", "UPDATE_EVENT", "CANCEL_TICKET", "PAYMENT_CONFIRMED", "PAYMENT_ALREADY_CONFIRMED", "REFUND", "REFUND_ATTEMPT_FAILED", "CHECK_IN", "MANUAL_CHECK_IN", "REVERT_CHECK_IN", "BADGE_SCAN", "UPDATE_TICKET", "TAG_TICKET", "UNTAG_TICKET", "UPDATE_TICKET_CATEGORY", "UPDATE_INVOICE", "FORCED_UPDATE_INVOICE", "TERMS_CONDITION_ACCEPTED", "PRIVACY_POLICY_ACCEPTED", "EXTERNAL_INVOICE_NUMBER", "EXTERNAL_CREDIT_NOTE_NUMBER", "VAT_VALIDATION_SUCCESSFUL", "VAT_FORMAL_VALIDATION_SUCCESSFUL", "VAT_VALIDATION_SKIPPED", "VAT_CUSTOM_CONFIGURATION_APPLIED", "GROUP_MEMBER_ACQUIRED", "CREDIT_NOTE_ISSUED", "BILLING_DATA_UPDATED", "BILLING_DOCUMENT_GENERATED", "BILLING_DOCUMENT_INVALIDATED", "BILLING_DOCUMENT_RESTORED", "FORCE_VAT_APPLICATION", "PAYMENT_FAILED", "MATCHING_PAYMENT_FOUND", "MATCHING_PAYMENT_DISCARDED", "AUTOMATIC_PAYMENT_CONFIRMATION", "AUTOMATIC_PAYMENT_CONFIRMATION_FAILED", "DYNAMIC_DISCOUNT_CODE_CREATED", "SUBSCRIPTION_ACQUIRED", "UPDATE_TICKET_METADATA", "WARNING_IGNORED" ] + }, + "eventTime" : { + "type" : "string", + "format" : "date-time" + }, + "entityType" : { + "type" : "string", + "enum" : [ "EVENT", "TICKET", "RESERVATION", "SUBSCRIPTION" ] + }, + "entityId" : { + "type" : "string" + }, + "modifications" : { + "type" : "array", + "items" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + }, + "username" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + } + } + }, + "ResultListAudit" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Audit" + } + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "PageAndContentListTicketReservation" : { + "type" : "object", + "properties" : { + "left" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketReservation" + } + }, + "right" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "EventBasicInfo" : { + "type" : "object", + "properties" : { + "shortName" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "publicIdentifier" : { + "type" : "string" + } + } + }, + "PromoCodeUsageResult" : { + "type" : "object", + "properties" : { + "promoCode" : { + "type" : "string" + }, + "event" : { + "$ref" : "#/components/schemas/EventBasicInfo" + }, + "reservations" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ReservationInfo" + } + } + } + }, + "PageAndContentListReservationPaymentDetail" : { + "type" : "object", + "properties" : { + "left" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ReservationPaymentDetail" + } + }, + "right" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ReservationPaymentDetail" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "paymentMethod" : { + "type" : "string" + }, + "paidAmount" : { + "type" : "string" + }, + "currencyCode" : { + "type" : "string" + }, + "transactionTimestamp" : { + "type" : "string" + }, + "transactionNotes" : { + "type" : "string" + }, + "invoiceNumber" : { + "type" : "string" + } + } + }, + "PaymentMethodDTO" : { + "type" : "object", + "properties" : { + "paymentProxy" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "paymentMethod" : { + "type" : "string", + "enum" : [ "CREDIT_CARD", "PAYPAL", "IDEAL", "BANK_TRANSFER", "ON_SITE", "NONE", "APPLE_PAY", "BANCONTACT", "ING_HOME_PAY", "BELFIUS", "KBC", "PRZELEWY_24", "ALIPAY", "POSTFINANCE", "TWINT" ] + }, + "status" : { + "type" : "string", + "enum" : [ "ACTIVE", "ERROR" ] + }, + "active" : { + "type" : "boolean" + }, + "onlyForCurrency" : { + "uniqueItems" : true, + "type" : "array", + "deprecated" : true, + "items" : { + "type" : "string" + } + } + } + }, + "EventSubscriptionLink" : { + "type" : "object", + "properties" : { + "subscriptionDescriptorId" : { + "type" : "string", + "format" : "uuid" + }, + "subscriptionDescriptorTitle" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "eventShortName" : { + "type" : "string" + }, + "eventDisplayName" : { + "type" : "string" + }, + "pricePerTicket" : { + "type" : "integer", + "format" : "int32" + }, + "eventCurrency" : { + "type" : "string" + }, + "formattedPricePerTicket" : { + "type" : "number" + } + } + }, + "PromoCodeDiscountWithFormattedTimeAndAmount" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "currencyCode" : { + "type" : "string" + }, + "dynamic" : { + "type" : "boolean" + }, + "description" : { + "type" : "string" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "promoCode" : { + "type" : "string" + }, + "utcStart" : { + "type" : "string", + "format" : "date-time" + }, + "utcEnd" : { + "type" : "string", + "format" : "date-time" + }, + "discountAmount" : { + "type" : "integer", + "format" : "int32" + }, + "discountType" : { + "type" : "string", + "enum" : [ "FIXED_AMOUNT", "PERCENTAGE", "FIXED_AMOUNT_RESERVATION", "NONE" ] + }, + "codeType" : { + "type" : "string", + "enum" : [ "DISCOUNT", "ACCESS", "DYNAMIC" ] + }, + "hiddenCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "maxUsage" : { + "type" : "integer", + "format" : "int32" + }, + "categories" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "currentlyValid" : { + "type" : "boolean" + }, + "emailReference" : { + "type" : "string" + }, + "expired" : { + "type" : "boolean" + }, + "fixedAmount" : { + "type" : "boolean" + }, + "formattedStart" : { + "type" : "string" + }, + "formattedDiscountAmount" : { + "type" : "string" + }, + "formattedEnd" : { + "type" : "string" + } + } + }, + "ProviderAndKeys" : { + "type" : "object", + "properties" : { + "provider" : { + "type" : "string", + "enum" : [ "GOOGLE", "HERE", "NONE" ] + }, + "keys" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + } + } + }, + "Group" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "active" : { + "type" : "boolean" + } + } + }, + "LinkedGroup" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "groupId" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "ticketCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string", + "enum" : [ "ONCE_PER_VALUE", "LIMITED_QUANTITY", "UNLIMITED" ] + }, + "matchType" : { + "type" : "string", + "enum" : [ "FULL", "EMAIL_DOMAIN" ] + }, + "maxAllocation" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "CapabilityDetail" : { + "type" : "object", + "properties" : { + "key" : { + "type" : "string" + }, + "label" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "selector" : { + "type" : "string" + } + } + }, + "ExtensionMetadata" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "version" : { + "type" : "integer", + "format" : "int32" + }, + "async" : { + "type" : "boolean" + }, + "events" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "parameters" : { + "$ref" : "#/components/schemas/Parameters" + }, + "capabilities" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "capabilityDetails" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/CapabilityDetail" + } + } + } + }, + "ExtensionSupport" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "path" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "hash" : { + "type" : "string" + }, + "enabled" : { + "type" : "boolean" + }, + "async" : { + "type" : "boolean" + }, + "script" : { + "type" : "string" + }, + "extensionMetadata" : { + "$ref" : "#/components/schemas/ExtensionMetadata" + } + } + }, + "Parameters" : { + "type" : "object", + "properties" : { + "fields" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Field" + } + }, + "configurationLevels" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + }, + "ExtensionParameterMetadataAndValue" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "extensionDisplayName" : { + "type" : "string" + }, + "configurationLevel" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "type" : { + "type" : "string" + }, + "mandatory" : { + "type" : "boolean" + }, + "path" : { + "type" : "string" + }, + "extensionId" : { + "type" : "integer", + "format" : "int32" + }, + "extensionName" : { + "type" : "string" + }, + "configurationPath" : { + "type" : "string" + }, + "configurationValue" : { + "type" : "string" + }, + "value" : { + "type" : "string" + }, + "componentType" : { + "type" : "string" + }, + "configurationPathLevel" : { + "type" : "string" + } + } + }, + "ExtensionLog" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "effectivePath" : { + "type" : "string" + }, + "path" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "type" : { + "type" : "string", + "enum" : [ "SUCCESS", "ERROR", "INFO", "WARNING" ] + }, + "timestamp" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "PageAndContentListExtensionLog" : { + "type" : "object", + "properties" : { + "left" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ExtensionLog" + } + }, + "right" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "EventStatistic" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "displayName" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "DRAFT", "PUBLIC", "DISABLED" ] + }, + "shortName" : { + "type" : "string" + }, + "warningNeeded" : { + "type" : "boolean" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "allowedPaymentProxies" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "fileBlobId" : { + "type" : "string" + }, + "notAllocatedTickets" : { + "type" : "integer", + "format" : "int32" + }, + "availableSeats" : { + "type" : "integer", + "format" : "int32" + }, + "checkedInTickets" : { + "type" : "integer", + "format" : "int32" + }, + "expired" : { + "type" : "boolean" + }, + "notSoldTickets" : { + "type" : "integer", + "format" : "int32" + }, + "soldTickets" : { + "type" : "integer", + "format" : "int32" + }, + "pendingTickets" : { + "type" : "integer", + "format" : "int32" + }, + "dynamicAllocation" : { + "type" : "integer", + "format" : "int32" + }, + "releasedTickets" : { + "type" : "integer", + "format" : "int32" + }, + "formattedEnd" : { + "type" : "string" + }, + "visibleForCurrentUser" : { + "type" : "boolean" + }, + "formattedBegin" : { + "type" : "string" + }, + "displayStatistics" : { + "type" : "boolean" + } + } + }, + "EventListItem" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "key" : { + "type" : "string" + }, + "location" : { + "type" : "string" + }, + "timeZone" : { + "type" : "string" + }, + "url" : { + "type" : "string" + }, + "begin" : { + "type" : "string" + }, + "end" : { + "type" : "string" + }, + "latitude" : { + "type" : "string" + }, + "longitude" : { + "type" : "string" + }, + "imageUrl" : { + "type" : "string" + }, + "apiVersion" : { + "type" : "integer", + "format" : "int32" + }, + "descriptions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PublicEventDescription" + } + }, + "oneDay" : { + "type" : "boolean" + } + } + }, + "PublicEventDescription" : { + "type" : "object", + "properties" : { + "locale" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "shortDescription" : { + "type" : "string" + } + } + }, + "EventAndOrganization" : { + "type" : "object", + "properties" : { + "event" : { + "$ref" : "#/components/schemas/EventWithAdditionalInfo" + }, + "organization" : { + "$ref" : "#/components/schemas/Organization" + } + } + }, + "TicketsByDateStatistic" : { + "type" : "object", + "properties" : { + "count" : { + "type" : "integer", + "format" : "int64" + }, + "date" : { + "type" : "string" + } + } + }, + "TicketsStatistics" : { + "type" : "object", + "properties" : { + "granularity" : { + "type" : "string" + }, + "sold" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketsByDateStatistic" + } + }, + "reserved" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketsByDateStatistic" + } + } + } + }, + "TicketReservationWithTransaction" : { + "type" : "object", + "properties" : { + "ticketReservation" : { + "$ref" : "#/components/schemas/TicketReservation" + }, + "billingDetails" : { + "$ref" : "#/components/schemas/BillingDetails" + }, + "transaction" : { + "$ref" : "#/components/schemas/Transaction" + }, + "ticketsCount" : { + "type" : "integer", + "format" : "int32" + }, + "promoCode" : { + "type" : "string" + }, + "specialPriceToken" : { + "type" : "string" + } + } + }, + "SerializablePairStringString" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "key" : { + "type" : "string" + }, + "right" : { + "type" : "string" + }, + "left" : { + "type" : "string" + } + } + }, + "PageAndContentListTicketWithStatistic" : { + "type" : "object", + "properties" : { + "left" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/TicketWithStatistic" + } + }, + "right" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "TicketFieldConfiguration" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "order" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string" + }, + "maxLength" : { + "type" : "integer", + "format" : "int32" + }, + "minLength" : { + "type" : "integer", + "format" : "int32" + }, + "required" : { + "type" : "boolean" + }, + "editable" : { + "type" : "boolean" + }, + "restrictedValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "context" : { + "type" : "string", + "enum" : [ "ATTENDEE", "ADDITIONAL_SERVICE" ] + }, + "additionalServiceId" : { + "type" : "integer", + "format" : "int32" + }, + "categoryIds" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + }, + "disabledValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "readOnly" : { + "type" : "boolean" + }, + "count" : { + "type" : "integer", + "format" : "int32" + }, + "inputType" : { + "type" : "string" + }, + "countryField" : { + "type" : "boolean" + }, + "inputField" : { + "type" : "boolean" + }, + "textareaField" : { + "type" : "boolean" + }, + "selectField" : { + "type" : "boolean" + }, + "checkboxField" : { + "type" : "boolean" + }, + "euVat" : { + "type" : "boolean" + }, + "maxLengthDefined" : { + "type" : "boolean" + }, + "minLengthDefined" : { + "type" : "boolean" + } + } + }, + "TicketFieldConfigurationAndAllDescriptions" : { + "type" : "object", + "properties" : { + "ticketFieldConfiguration" : { + "$ref" : "#/components/schemas/TicketFieldConfiguration" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "$ref" : "#/components/schemas/TicketFieldDescription" + } + }, + "name" : { + "type" : "string" + }, + "context" : { + "type" : "string", + "enum" : [ "ATTENDEE", "ADDITIONAL_SERVICE" ] + }, + "id" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string" + }, + "readOnly" : { + "type" : "boolean" + }, + "count" : { + "type" : "integer", + "format" : "int32" + }, + "inputType" : { + "type" : "string" + }, + "order" : { + "type" : "integer", + "format" : "int32" + }, + "minLength" : { + "type" : "integer", + "format" : "int32" + }, + "maxLength" : { + "type" : "integer", + "format" : "int32" + }, + "required" : { + "type" : "boolean" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "additionalServiceId" : { + "type" : "integer", + "format" : "int32" + }, + "countryField" : { + "type" : "boolean" + }, + "restrictedValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "editable" : { + "type" : "boolean" + }, + "disabledValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "inputField" : { + "type" : "boolean" + }, + "textareaField" : { + "type" : "boolean" + }, + "selectField" : { + "type" : "boolean" + }, + "checkboxField" : { + "type" : "boolean" + }, + "euVat" : { + "type" : "boolean" + }, + "maxLengthDefined" : { + "type" : "boolean" + }, + "minLengthDefined" : { + "type" : "boolean" + }, + "categoryIds" : { + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } + } + } + }, + "TicketFieldDescription" : { + "type" : "object", + "properties" : { + "ticketFieldConfigurationId" : { + "type" : "integer", + "format" : "int32" + }, + "locale" : { + "type" : "string" + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + } + } + }, + "RestrictedValueStats" : { + "type" : "object", + "properties" : { + "name" : { + "type" : "string" + }, + "count" : { + "type" : "integer", + "format" : "int32" + }, + "percentage" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "Event" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "organizationId" : { + "type" : "integer", + "format" : "int32" + }, + "format" : { + "type" : "string", + "enum" : [ "IN_PERSON", "ONLINE", "HYBRID" ] + }, + "shortName" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "websiteUrl" : { + "type" : "string" + }, + "externalUrl" : { + "type" : "string" + }, + "termsAndConditionsUrl" : { + "type" : "string" + }, + "privacyPolicyUrl" : { + "type" : "string" + }, + "imageUrl" : { + "type" : "string" + }, + "fileBlobId" : { + "type" : "string" + }, + "location" : { + "type" : "string" + }, + "latitude" : { + "type" : "string" + }, + "longitude" : { + "type" : "string" + }, + "begin" : { + "type" : "string", + "format" : "date-time" + }, + "end" : { + "type" : "string", + "format" : "date-time" + }, + "currency" : { + "type" : "string" + }, + "vatIncluded" : { + "type" : "boolean" + }, + "allowedPaymentProxies" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + } + }, + "timeZone" : { + "type" : "string" + }, + "locales" : { + "type" : "integer", + "format" : "int32" + }, + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + }, + "version" : { + "type" : "string" + }, + "status" : { + "type" : "string", + "enum" : [ "DRAFT", "PUBLIC", "DISABLED" ] + }, + "type" : { + "type" : "string", + "enum" : [ "subscription", "event" ] + }, + "title" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + } + }, + "publicIdentifier" : { + "type" : "string" + }, + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + }, + "free" : { + "type" : "boolean" + }, + "freeOfCharge" : { + "type" : "boolean" + }, + "sameDay" : { + "type" : "boolean" + }, + "online" : { + "type" : "boolean" + }, + "regularPrice" : { + "type" : "number" + }, + "imageIsPresent" : { + "type" : "boolean" + }, + "fileBlobIdIsPresent" : { + "type" : "boolean" + }, + "multiplePaymentMethods" : { + "type" : "boolean" + }, + "firstPaymentMethod" : { + "type" : "string", + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "useFirstAndLastName" : { + "type" : "boolean" + }, + "privacyPolicyLinkOrNull" : { + "type" : "string" + }, + "beginTimeZoneOffset" : { + "type" : "integer", + "format" : "int32" + }, + "endTimeZoneOffset" : { + "type" : "integer", + "format" : "int32" + }, + "isOnline" : { + "type" : "boolean" + }, + "firstContentLanguage" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + } + }, + "WaitingQueueSubscription" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "creation" : { + "type" : "string", + "format" : "date-time" + }, + "eventId" : { + "type" : "integer", + "format" : "int32" + }, + "status" : { + "type" : "string", + "enum" : [ "WAITING", "PENDING", "ACQUIRED", "EXPIRED", "CANCELLED" ] + }, + "fullName" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "emailAddress" : { + "type" : "string" + }, + "reservationId" : { + "type" : "string" + }, + "userLanguage" : { + "type" : "string" + }, + "selectedCategoryId" : { + "type" : "integer", + "format" : "int32" + }, + "subscriptionType" : { + "type" : "string", + "enum" : [ "PRE_SALES", "SOLD_OUT" ] + }, + "locale" : { + "type" : "object", + "properties" : { + "language" : { + "type" : "string" + }, + "script" : { + "type" : "string" + }, + "variant" : { + "type" : "string" + }, + "displayName" : { + "type" : "string" + }, + "country" : { + "type" : "string" + }, + "unicodeLocaleAttributes" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "unicodeLocaleKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "displayLanguage" : { + "type" : "string" + }, + "displayScript" : { + "type" : "string" + }, + "displayCountry" : { + "type" : "string" + }, + "displayVariant" : { + "type" : "string" + }, + "extensionKeys" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "iso3Language" : { + "type" : "string" + }, + "iso3Country" : { + "type" : "string" + } + } + }, + "preSales" : { + "type" : "boolean" + } + } + }, + "AdminReservationRequestStats" : { + "type" : "object", + "properties" : { + "requestId" : { + "type" : "string" + }, + "userId" : { + "type" : "integer", + "format" : "int64" + }, + "eventId" : { + "type" : "integer", + "format" : "int64" + }, + "countSuccess" : { + "type" : "integer", + "format" : "int32" + }, + "countPending" : { + "type" : "integer", + "format" : "int32" + }, + "countError" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "ResultAdminReservationRequestStats" : { + "type" : "object", + "properties" : { + "status" : { + "type" : "string", + "enum" : [ "OK", "VALIDATION_ERROR", "ERROR" ] + }, + "data" : { + "$ref" : "#/components/schemas/AdminReservationRequestStats" + }, + "errors" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ErrorCode" + } + }, + "success" : { + "type" : "boolean" + }, + "formattedErrors" : { + "type" : "string" + }, + "firstErrorOrNull" : { + "$ref" : "#/components/schemas/ErrorCode" + } + } + }, + "DynamicFieldTemplate" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int32" + }, + "name" : { + "type" : "string" + }, + "type" : { + "type" : "string" + }, + "restrictedValues" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : { + "type" : "object", + "additionalProperties" : { + "type" : "object" + } + }, + "maxLength" : { + "type" : "integer", + "format" : "int32" + }, + "minLength" : { + "type" : "integer", + "format" : "int32" + } + } + }, + "InstanceSettings" : { + "type" : "object", + "properties" : { + "descriptionMaxLength" : { + "type" : "integer", + "format" : "int32" + }, + "baseUrl" : { + "type" : "string" + } + } + }, + "PairStringString" : { + "type" : "object", + "properties" : { + "value" : { + "type" : "string" + }, + "key" : { + "type" : "string" + }, + "right" : { + "type" : "string" + }, + "left" : { + "type" : "string" + } + } + }, + "Content" : { + "type" : "object", + "properties" : { + "firstRow" : { + "type" : "string" + }, + "secondRow" : { + "type" : "string" + }, + "thirdRow" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "additionalRows" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "checkbox" : { + "type" : "boolean" + } + } + }, + "General" : { + "type" : "object", + "properties" : { + "printPartialID" : { + "type" : "boolean" + } + } + }, + "LabelLayout" : { + "type" : "object", + "properties" : { + "qrCode" : { + "$ref" : "#/components/schemas/QRCode" + }, + "content" : { + "$ref" : "#/components/schemas/Content" + }, + "general" : { + "$ref" : "#/components/schemas/General" + }, + "mediaName" : { + "type" : "string" + } + } + }, + "QRCode" : { + "type" : "object", + "properties" : { + "additionalInfo" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "infoSeparator" : { + "type" : "string" + } + } + }, + "AttendeeSearchResults" : { + "type" : "object", + "properties" : { + "totalResults" : { + "type" : "integer", + "format" : "int32" + }, + "checkedIn" : { + "type" : "integer", + "format" : "int32" + }, + "totalPages" : { + "type" : "integer", + "format" : "int32" + }, + "numPage" : { + "type" : "integer", + "format" : "int32" + }, + "attendees" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Attendee" + } + } + } + }, + "CheckInStatistics" : { + "type" : "object", + "properties" : { + "totalAttendees" : { + "type" : "integer", + "format" : "int32" + }, + "checkedIn" : { + "type" : "integer", + "format" : "int32" + }, + "lastUpdate" : { + "type" : "integer", + "format" : "int64" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/custom-tax-policy-extension.js b/src/test/resources/custom-tax-policy-extension.js new file mode 100644 index 0000000000..04118780e0 --- /dev/null +++ b/src/test/resources/custom-tax-policy-extension.js @@ -0,0 +1,56 @@ +/** + * The script metadata object describes whether your extension should be invoked asynchronously, and which events it supports + * @returns {{ async: boolean, events: string[] }} + */ +function getScriptMetadata() { + return { + id: 'customTaxPolicyApplicationExample', // optional: id and version will be used later as a mechanism for checking if the script has a newer version + displayName: 'Custom Tax Policy Application Example', //mandatory: the name displayed in the configuration page + version: 0, // optional + async: false, + events: [ + 'CUSTOM_TAX_POLICY_APPLICATION' + ] + }; +} +/** + * Executes the extension. + * @param scriptEvent + * @returns Object + */ +function executeScript(scriptEvent) { + console.log('entering customTaxPolicyApplicationExample for reservation', reservationId); + // in this simple example we need to remove taxes if the attendee email ends with "@example.org" + var keys = Object.keys(reservationForm.tickets); + var containsModifiedElements = false; + var out = []; + for (i = 0; i < keys.length; i++) { + var uuid = keys[i]; + var attendee = reservationForm.tickets[uuid]; + var ticketInfo = ticketInfoByUuid[uuid]; + var category = categoriesById[ticketInfo.ticketCategoryId]; + var originalTaxPolicy = ticketInfo.taxPolicy; + if (!category.free && attendee.email.endsWith('@example.org')) { + console.log('found attendee with matching email!'); + out.push({ + uuid: uuid, + taxPolicy: originalTaxPolicy == 'INCLUDED' ? 'CUSTOM_INCLUDED_EXEMPT' : 'CUSTOM_NOT_INCLUDED_EXEMPT' + }); + containsModifiedElements = true; + } else { + out.push({ + uuid: uuid, + taxPolicy: originalTaxPolicy + }); + } + } + + if (!containsModifiedElements) { + // since nothing was modified, we return "null" to continue with the original validation process + return null; + } + + return { + ticketPolicies: out + }; +} diff --git a/src/test/resources/images/README.md b/src/test/resources/images/README.md new file mode 100644 index 0000000000..115607175d --- /dev/null +++ b/src/test/resources/images/README.md @@ -0,0 +1 @@ +Image credit: NASA, ESA, CSA, and STScI \ No newline at end of file diff --git a/src/test/resources/images/main_image_star-forming_region_carina_reduced.jpg b/src/test/resources/images/main_image_star-forming_region_carina_reduced.jpg new file mode 100644 index 0000000000..b811d098a2 Binary files /dev/null and b/src/test/resources/images/main_image_star-forming_region_carina_reduced.jpg differ diff --git a/src/test/resources/init-db-user.sql b/src/test/resources/init-db-user.sql index f1e2977306..324141dfb9 100644 --- a/src/test/resources/init-db-user.sql +++ b/src/test/resources/init-db-user.sql @@ -16,4 +16,4 @@ -- create role alfio_user with NOSUPERUSER NOINHERIT LOGIN ENCRYPTED PASSWORD 'password'; -alter database alfio owner to alfio_user; \ No newline at end of file +GRANT USAGE, CREATE ON SCHEMA public TO alfio_user; \ No newline at end of file diff --git a/src/test/resources/retry-reservation/fail-invoice-number-generator.js b/src/test/resources/retry-reservation/fail-invoice-number-generator.js new file mode 100644 index 0000000000..658ee86969 --- /dev/null +++ b/src/test/resources/retry-reservation/fail-invoice-number-generator.js @@ -0,0 +1,23 @@ +/** + * The script metadata object describes whether or not your extension should be invoked asynchronously, and which events it supports + * @returns {{ async: boolean, events: string[] }} + */ +function getScriptMetadata() { + return { + id: 'myExtensionIdentifier', + displayName: 'My Extension', + version: 0, // optional + async: false, + events: [ + 'INVOICE_GENERATION' + ] + }; +} +/** + * Executes the extension. + * @param scriptEvent + * @returns Object + */ +function executeScript(scriptEvent) { + throw 'error during process'; +} diff --git a/src/test/resources/retry-reservation/fail-ticket-metadata.js b/src/test/resources/retry-reservation/fail-ticket-metadata.js new file mode 100644 index 0000000000..cce26a1117 --- /dev/null +++ b/src/test/resources/retry-reservation/fail-ticket-metadata.js @@ -0,0 +1,23 @@ +/** + * The script metadata object describes whether or not your extension should be invoked asynchronously, and which events it supports + * @returns {{ async: boolean, events: string[] }} + */ +function getScriptMetadata() { + return { + id: 'myExtensionIdentifier', + displayName: 'My Extension', + version: 0, // optional + async: false, + events: [ + 'TICKET_ASSIGNED_GENERATE_METADATA' + ] + }; +} +/** + * Executes the extension. + * @param scriptEvent + * @returns Object + */ +function executeScript(scriptEvent) { + throw 'error during process'; +} \ No newline at end of file diff --git a/src/test/resources/retry-reservation/success-invoice-number-generator.js b/src/test/resources/retry-reservation/success-invoice-number-generator.js new file mode 100644 index 0000000000..f65abe631e --- /dev/null +++ b/src/test/resources/retry-reservation/success-invoice-number-generator.js @@ -0,0 +1,25 @@ +/** + * The script metadata object describes whether or not your extension should be invoked asynchronously, and which events it supports + * @returns {{ async: boolean, events: string[] }} + */ +function getScriptMetadata() { + return { + id: 'myExtensionIdentifier', + displayName: 'My Extension', + version: 0, // optional + async: false, + events: [ + 'INVOICE_GENERATION' + ] + }; +} +/** + * Executes the extension. + * @param scriptEvent + * @returns Object + */ +function executeScript(scriptEvent) { + return { + invoiceNumber: 'ABCD' + }; +} diff --git a/src/test/resources/retry-reservation/success-ticket-metadata.js b/src/test/resources/retry-reservation/success-ticket-metadata.js new file mode 100644 index 0000000000..539bd4da57 --- /dev/null +++ b/src/test/resources/retry-reservation/success-ticket-metadata.js @@ -0,0 +1,27 @@ +/** + * The script metadata object describes whether or not your extension should be invoked asynchronously, and which events it supports + * @returns {{ async: boolean, events: string[] }} + */ +function getScriptMetadata() { + return { + id: 'myExtensionIdentifier', + displayName: 'My Extension', + version: 0, // optional + async: false, + events: [ + 'TICKET_ASSIGNED_GENERATE_METADATA' + ] + }; +} +/** + * Executes the extension. + * @param scriptEvent + * @returns Object + */ +function executeScript(scriptEvent) { + return { + attributes: { + uuid: ticket.uuid + } + }; +} \ No newline at end of file diff --git a/src/test/resources/transaction-json/stripe-success-valid-v2022-11-15.json b/src/test/resources/transaction-json/stripe-success-valid-v2022-11-15.json new file mode 100644 index 0000000000..bf5de35919 --- /dev/null +++ b/src/test/resources/transaction-json/stripe-success-valid-v2022-11-15.json @@ -0,0 +1,74 @@ +{ + "id": "evt_ID", + "object": "event", + "api_version": "2022-11-15", + "created": 1671384724, + "data": { + "object": { + "id": "pi_ID", + "object": "payment_intent", + "amount": 1000, + "amount_capturable": 0, + "amount_details": { + "tip": { + } + }, + "amount_received": 1000, + "application": null, + "application_fee_amount": null, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "client_secret": "pi_ID_secret_ID", + "confirmation_method": "automatic", + "created": 1671384722, + "currency": "chf", + "customer": null, + "description": "1 ticket(s) for event Event Name", + "invoice": null, + "last_payment_error": null, + "latest_charge": "ch_ID", + "livemode": false, + "metadata": { + "fullName": "Test McTest", + "alfioBaseUrl": "https://alf.io", + "billingAddress": "Test McTest,Bahnhofstrasse 1,8000 Zürich,Switzerland", + "reservationId": "RESERVATION_ID", + "email": "noreply@example.org" + }, + "next_action": null, + "on_behalf_of": null, + "payment_method": "pm_ID", + "payment_method_options": { + "card": { + "installments": null, + "mandate_options": null, + "network": null, + "request_three_d_secure": "automatic" + } + }, + "payment_method_types": [ + "card" + ], + "processing": null, + "receipt_email": null, + "review": null, + "setup_future_usage": null, + "shipping": null, + "source": null, + "statement_descriptor": null, + "statement_descriptor_suffix": null, + "status": "succeeded", + "transfer_data": null, + "transfer_group": null + } + }, + "livemode": false, + "pending_webhooks": 3, + "request": { + "id": null, + "idempotency_key": "pi_ID-src_ID" + }, + "type": "payment_intent.succeeded" +} \ No newline at end of file diff --git a/src/test/resources/transaction-json/stripe-success-valid.json b/src/test/resources/transaction-json/stripe-success-valid.json index 362d15299a..4bec6efc22 100644 --- a/src/test/resources/transaction-json/stripe-success-valid.json +++ b/src/test/resources/transaction-json/stripe-success-valid.json @@ -145,7 +145,7 @@ "fullName": "Test McTest", "alfioBaseUrl": "https://alf.io", "billingAddress": "Test McTest,Bahnhofstrasse 1,8000 Zürich,Switzerland", - "reservationId": "abcdefg", + "reservationId": "RESERVATION_ID", "email": "noreply@example.org" }, "next_action": null, diff --git a/src/test/resources/wallet-json/event-class.json b/src/test/resources/wallet-json/event-class.json new file mode 100644 index 0000000000..d109018fb2 --- /dev/null +++ b/src/test/resources/wallet-json/event-class.json @@ -0,0 +1,84 @@ +{ + "id": "ISSUER_ID.EVENT_CLASS_ID", + "eventId": "1", + "multipleDevicesAndHoldersAllowedStatus": "ONE_USER_ALL_DEVICES", + "issuerName": "Devoxx Belgium 2022", + "eventName": { + "defaultValue": { + "language": "en-US", + "value": "Speaker" + } + }, + "venue": { + "address": { + "defaultValue": { + "language": "en-US", + "value": "Antwerp Kinepolis Belgium" + } + }, + "name": { + "defaultValue": { + "language": "en-US", + "value": "Antwerp Kinepolis Belgium" + } + } + }, + "dateTime": { + "start": "2022-10-10T08:00:00.00Z", + "end": "2022-10-14T14:00:00.00Z" + }, + "reviewStatus": "UNDER_REVIEW", + "hexBackgroundColor": "#FFFFFF", + "logo": { + "sourceUri": { + "uri": "https://reg.devoxx.be/file/c73315d4890c5fade112165c40338b72271e43939eed4e1e57a4e4891ee19cc8" + } + }, + "classTemplateInfo": { + "cardTemplateOverride": { + "cardRowTemplateInfos": [ + { + "oneItem": { + "item": { + "firstValue": { + "fields": [ + { + "fieldPath": "class.dateTime.start" + } + ] + } + } + } + }, + { + "oneItem": { + "item": { + "firstValue": { + "fields": [ + { + "fieldPath": "class.dateTime.end" + } + ] + } + } + } + } + ] + } + }, + "linksModuleData": { + "uris": [ + { + "uri": "https://alf.io", + "description": "Powered by Alf.io, the Open Source ticket reservation system.", + "id": "alfio" + } + ] + }, + "messages": [ + { + "header": "Event Description", + "body": null + } + ] +} \ No newline at end of file diff --git a/src/test/resources/wallet-json/event-object.json b/src/test/resources/wallet-json/event-object.json new file mode 100644 index 0000000000..d3f15f6d78 --- /dev/null +++ b/src/test/resources/wallet-json/event-object.json @@ -0,0 +1,11 @@ +{ + "id": "ISSUER_ID.MEMBER_ID", + "classId": "ISSUER_ID.EVENT_CLASS_ID", + "state": "ACTIVE", + "ticketHolderName": "Alf", + "ticketNumber": "123-321-000", + "barcode": { + "type": "QR_CODE", + "value": "123456789" + } +} \ No newline at end of file diff --git a/src/test/resources/wallet-json/generic-object.json b/src/test/resources/wallet-json/generic-object.json new file mode 100644 index 0000000000..8dd8f6bd5e --- /dev/null +++ b/src/test/resources/wallet-json/generic-object.json @@ -0,0 +1,50 @@ +{ + "id": "ISSUER_ID.OBJECT_ID", + "classId": "ISSUER_ID.GENERIC_CLASS_ID", + "cardTitle": { + "defaultValue": { + "language": "en-US", + "value": "Test Event" + } + }, + "header": { + "defaultValue": { + "language": "en-US", + "value": "General admission" + } + }, + "subheader": { + "defaultValue": { + "language": "en-US", + "value": "" + } + }, + "logo": { + "sourceUri": { + "uri": "https://devoxx.be/wp-content/uploads/2019/05/DEVOXX-Name-Only-TransparentBackground.png", + "description": "" + } + }, + "hexBackgroundColor": "#ffffff", + "textModulesData": [ + { + "header": "Venue", + "body": "Palazzo dei congressi Lugano, Switzerland", + "id": "Row1Left" + }, + { + "header": "Valid From", + "body": "Mon, Oct 10", + "id": "Row2Left" + }, + { + "header": "Valid Until", + "body": "Tue, Oct 11", + "id": "Row3Left" + } + ], + "barcode": { + "type": "QR_CODE", + "value": "123456789" + } +} \ No newline at end of file diff --git a/website/config.toml b/website/config.toml index 5b091b479d..c9cd37a09f 100644 --- a/website/config.toml +++ b/website/config.toml @@ -122,7 +122,7 @@ no = 'Sorry to hear that. Please <a href="https://github.com/USERNAME/REPOSITORY # End user relevant links. These will show up on left side of footer and in the community page if you have one. [[params.links.user]] name = "User mailing list" - url = "https://groups.google.com/d/forum/alfio" + url = "https://github.com/alfio-event/alf.io/discussions" icon = "fa fa-envelope" desc = "Discussion and help from your fellow users" [[params.links.user]] diff --git a/website/content/en/docs/Configuration/Invoice/_index.md b/website/content/en/docs/Configuration/Invoice/_index.md index 630ad78d44..d0856214d6 100644 --- a/website/content/en/docs/Configuration/Invoice/_index.md +++ b/website/content/en/docs/Configuration/Invoice/_index.md @@ -77,8 +77,8 @@ If you want to regenerate/download an invoice for a reservation, head to the Res ![Download Single Invoice](/img/configuration/invoice/standard/005.png) -#### Download all invoices for an event +#### Download all Billing Documents for an event -You can download all the invoices generated for an event, by selecting _Download_ -> _all invoices_ on the event detail page +You can download all the invoices generated for an event, by selecting _Download_ -> _all Billing Documents_ on the event detail page -![Download All Invoices](/img/configuration/invoice/standard/006.png) \ No newline at end of file +![Download All Billing Documents](/img/configuration/invoice/standard/006.png) \ No newline at end of file diff --git a/website/content/en/docs/Configuration/Payment/Mollie.md b/website/content/en/docs/Configuration/Payment/Mollie.md new file mode 100644 index 0000000000..4e469198dc --- /dev/null +++ b/website/content/en/docs/Configuration/Payment/Mollie.md @@ -0,0 +1,35 @@ +--- +title: "How-to set-up Mollie" +linkTitle: "Mollie" +date: 2022-05-20 +weight: 2 +description: > + How-to set-up Mollie as payment provider +--- +#### Support +Not all the payment methods supported by Mollie are supported by Alf.io + +The following payment methods are supported by Alf.io: +- IDEAL +- Creditcard +- Apple Pay +- Bankcontact +- ING home pay +- Belfius +- Przelewy24 +- KBC + +## How-to set-up Mollie as payment provider +To start using Mollie as your payment provider in Alf.io you first have to obtain two things from your Mollie dashboard. Your profile id and your api key (test or live). + +### Obtaining the required info from your Mollie dashboard +To obtain the Profile ID and API-key, go to your [Mollie dashboard](https://my.mollie.com/dashboard/) In the left navigational-column of the dashboard click on Developers, then choose API-keys. Now select the appropriate website profile and copy the Profile-ID and API-key. +![developers section in Mollie dashboard](/website/static/img/configuration/Mollie/API-keys.png) +### Configuring Alf.io for Mollie +To set-up Alf.io for Mollie usage, go to Configuration and click on Payment. Then scroll to Mollie configuration. +1. Enable the configuration +2. If you have a Live-API key enable Live mode, if you have a test key disable Live mode. +3. Paste the API key +4. Paste your Profile ID in the box. + +#### Dont forget to save the configurations by clicking on save on the bottom of the page. And test Mollie by making a reservation, before production use. diff --git a/website/content/en/docs/Configuration/Wallet integration/_index.md b/website/content/en/docs/Configuration/Wallet integration/_index.md index eabb460b70..65a0faeabe 100644 --- a/website/content/en/docs/Configuration/Wallet integration/_index.md +++ b/website/content/en/docs/Configuration/Wallet integration/_index.md @@ -1,44 +1,7 @@ --- -title: "Apple(tm) Wallet Integration" -linkTitle: "Apple(tm) Wallet" +title: "Mobile Wallet Integration" +linkTitle: "Mobile Wallet" weight: 5 description: > - How to generate Apple(tm) passes for your tickets + How to let your users download their ticket on mobile devices --- - -## How to configure Apple (tm) Pass integration - -### create a new Apple (tm) Pass ID - -access the [create Pass ID](https://developer.apple.com/account/ios/identifier/passTypeId/create) page and follow the on-screen instruction to generate your new Pass ID and the corresponding certificates. - -### Download the certificate on your disk - -download the certificate on your disk, as suggested during the generation process, then double-click on it to install in Keychain Access - -### Export certificate - -make sure to select "My Certificates" under "Category", as shown by the screenshot below - -![](/img/configuration/apple-pass/category.png) - -Select your pass from the list - -![](/img/configuration/apple-pass/export.png) - -open context menu, then select "Export Pass ..." and export it as P12 file. -Please make sure to enter a strong password, you'll need it afterwards. - -### Import certificate in alf.io - -open a terminal, go to the folder where the exported P12 is, then execute the following command: - -> $ base64 certificate.p12 - -and copy the output. - -then open you alf.io instance and set the relevant options: - -![](/img/configuration/apple-pass/alfio-options.png) - -that's it! Enjoy Apple (tm) Pass integration! diff --git a/website/content/en/docs/Configuration/Wallet integration/apple-pass.md b/website/content/en/docs/Configuration/Wallet integration/apple-pass.md new file mode 100644 index 0000000000..10d814212c --- /dev/null +++ b/website/content/en/docs/Configuration/Wallet integration/apple-pass.md @@ -0,0 +1,44 @@ +--- +title: "Apple (tm) Pass Integration" +linkTitle: "Apple (tm) Pass" +weight: 5 +description: > + How to generate Apple(tm) passes for your tickets +--- + +## How to configure Apple (tm) Pass integration + +### create a new Apple (tm) Pass ID + +access the [create Pass ID](https://developer.apple.com/account/ios/identifier/passTypeId/create) page and follow the on-screen instruction to generate your new Pass ID and the corresponding certificates. + +### Download the certificate on your disk + +download the certificate on your disk, as suggested during the generation process, then double-click on it to install in Keychain Access + +### Export certificate + +make sure to select "My Certificates" under "Category", as shown by the screenshot below + +![](/img/configuration/apple-pass/category.png) + +Select your pass from the list + +![](/img/configuration/apple-pass/export.png) + +open context menu, then select "Export Pass ..." and export it as P12 file. +Please make sure to enter a strong password, you'll need it afterwards. + +### Import certificate in alf.io + +open a terminal, go to the folder where the exported P12 is, then execute the following command: + +> $ base64 certificate.p12 + +and copy the output. + +then open you alf.io instance and set the relevant options: + +![](/img/configuration/apple-pass/alfio-options.png) + +that's it! Enjoy Apple (tm) Pass integration! \ No newline at end of file diff --git a/website/content/en/docs/Configuration/Wallet integration/google-wallet.md b/website/content/en/docs/Configuration/Wallet integration/google-wallet.md new file mode 100644 index 0000000000..a5b1772c49 --- /dev/null +++ b/website/content/en/docs/Configuration/Wallet integration/google-wallet.md @@ -0,0 +1,45 @@ +--- +title: "Google(tm) Wallet Integration" +linkTitle: "Google(tm) Wallet" +weight: 6 +description: > + How to generate Google(tm) passes for your tickets +--- + +## How to configure Google (tm) Wallet integration + +Alf.io supports [Google(tm) Wallet](https://developers.google.com/wallet). Once the integration is active, attendees can save their tickets on Wallet, available on all their devices. + +### Configure and activate your issuer account + +follow steps **1 to 4** of the [Official prerequisites](https://developers.google.com/wallet/tickets/events/web/prerequisites); +Alf.io will take care of generating a wallet class for your event when needed. + +### Configure alf.io to enable Wallet integration + +Go to the system configuration, then find the "Mobile wallet integration" section and fill the required configuration: + +![Configuration for Google Wallet](/img/configuration/google-wallet/001-settings.png) + +#### Enable integration + +This flags allows you to enable/disable the integration with Google (tm) Wallet + +#### Overwrite previous EventClass and EventObject definitions + +Activate this flag if you want to force update on the EventClass. For example, if you change properties that have an impact on the attendees' tickets, like event date, ticket validity and so on + +#### Google Wallet issuer ID + +The issuer ID that you see on the Google Wallet API Dashboard + +#### Google Wallet Service Account Key in JSON format + +The service account key alf.io will use to authenticate against the Wallet API + +1. Follow the [Official guide](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) to generate a new Key for the service account you've previously created. +2. Download the key file as json, open it using a text editor and copy its content +3. Paste the content in the alf.io admin + + +after this configuration, your attendees will be able to save their tickets into Google (tm) Wallet! \ No newline at end of file diff --git a/website/content/en/docs/Reference/Extensions/reference/subscription.md b/website/content/en/docs/Reference/Extensions/reference/subscription.md new file mode 100644 index 0000000000..5f70955aa2 --- /dev/null +++ b/website/content/en/docs/Reference/Extensions/reference/subscription.md @@ -0,0 +1,43 @@ +--- +title: "Subscription" +linkTitle: "Subscription" +weight: 4 +date: 2023-02-05 +description: > + Compatible Application Events for the "Subscription" entity +--- + +### Customize subscription metadata +`SUBSCRIPTION_ASSIGNED_GENERATE_METADATA` + +Fired **synchronously** before marking a subscription as "acquired". The purpose of this extension is to allow metadata customization. + +A result of type [`SubscriptionMetadata`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/metadata/SubscriptionMetadata.java) is expected. Return `null` if you don't need to modify the current metadata. +<div class="table-responsive table-hover"> + <table class="table table-sm"> + <thead> + <tr> + <th>Variable</th> + <th>Type</th> + <th>About</th> + </tr> + </thead> + <tbody> + <tr> + <td>`subscription`</td> + <td>[`Subscription`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/subscription/Subscription.java)</td> + <td>Details about the subscription to be acquired</td> + </tr> + <tr> + <td>`subscriptionDescriptor`</td> + <td>[`SubscriptionDescriptor`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/subscription/SubscriptionDescriptor.java)</td> + <td>Subscription configuration (template)</td> + </tr> + <tr> + <td>`metadata`</td> + <td>[`SubscriptionMetadata`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/metadata/SubscriptionMetadata.java)</td> + <td>Existing metadata for subscription.</td> + </tr> + </tbody> + </table> +</div> \ No newline at end of file diff --git a/website/static/img/configuration/google-wallet/001-settings.png b/website/static/img/configuration/google-wallet/001-settings.png new file mode 100644 index 0000000000..1c6c009db9 Binary files /dev/null and b/website/static/img/configuration/google-wallet/001-settings.png differ diff --git a/website/static/img/configuration/invoice/standard/006.png b/website/static/img/configuration/invoice/standard/006.png index 7f9d21a5f6..4d841e865a 100644 Binary files a/website/static/img/configuration/invoice/standard/006.png and b/website/static/img/configuration/invoice/standard/006.png differ