diff --git a/.github/workflows/build-gradle.yml b/.github/workflows/build-gradle.yml new file mode 100644 index 00000000..9f44ccda --- /dev/null +++ b/.github/workflows/build-gradle.yml @@ -0,0 +1,36 @@ +#This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + + + name: Java CI with Gradle + + on: + [push, pull_request] + + permissions: + contents: read + + jobs: + build: + + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./backend/sportsmatch + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Execute Gradle build + run: ./gradlew build \ No newline at end of file diff --git a/.github/workflows/build-react.yml b/.github/workflows/build-react.yml new file mode 100644 index 00000000..80127883 --- /dev/null +++ b/.github/workflows/build-react.yml @@ -0,0 +1,28 @@ +name: Build and Test React + +on: [ push, pull_request ] + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./frontend/sportsmatch-app + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.1 + with: + node-version: latest + + - name: Install dependencies + run: npm install + + - name: Build frontend + run: npm run build + + - name: Run test + run: npm test \ No newline at end of file diff --git a/.github/workflows/checkstyle-react.yml b/.github/workflows/checkstyle-react.yml new file mode 100644 index 00000000..5b921f18 --- /dev/null +++ b/.github/workflows/checkstyle-react.yml @@ -0,0 +1,25 @@ +name: Check the React source code + +on: [ push, pull_request ] + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./frontend/sportsmatch-app + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4.0.1 + with: + node-version: latest + + - name: Install dependencies + run: npm install + + - name: Lint + run: npm run lint \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 3f0d8d3e..00000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,33 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - - -#### Uncoment this once you setup spring boot, it will run automatic tests and code style checks -# name: Java CI with Gradle - -# on: -# [push, pull_request] - -# permissions: -# contents: read - -# jobs: -# build: - -# runs-on: ubuntu-latest - -# steps: -# - uses: actions/checkout@v3 -# - name: Set up JDK 17 -# uses: actions/setup-java@v3 -# with: -# java-version: '17' -# distribution: 'temurin' -# - name: Build with Gradle -# uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 -# with: -# arguments: build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f5f18659..49ab7c2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,8 @@ ## Workflow 1. Create a feature branch when you start to work on a story and commit your changes to this - - the branch should be named `: `, for example `TB-3: Add login endpoint` - - the commits should also be named `: `, for example `TB-3: added unit tests` + - the branch should be named `: `, for example `SMA 3 Add login endpoint` + - the commits should also be named `: `, for example `SMA 3 added unit tests` 2. Push this frequently to the remote repository from your local 2. When the feature is done, create a Pull Request from the `feature_branch` to `development`, follow the guidelines 3. When the PR is approved, merge it, and delete your feature branch @@ -19,7 +19,7 @@ Read this article how to write meaningful commit messages: [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) We follow the rules below: -- **Commit message format**: `TB-{id}: ` +- **Commit message format**: `SMA {id} ` - **id**: Id of the ticket you are working on (in jira) - **Subject**: Changes in the commit diff --git a/backend/sportsmatch/.gitignore b/backend/sportsmatch/.gitignore new file mode 100644 index 00000000..dbdc3dc9 --- /dev/null +++ b/backend/sportsmatch/.gitignore @@ -0,0 +1,41 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### h2 database ### +./sportsmatch +*.db \ No newline at end of file diff --git a/backend/sportsmatch/build.gradle b/backend/sportsmatch/build.gradle new file mode 100644 index 00000000..8fa3d671 --- /dev/null +++ b/backend/sportsmatch/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.7' + id 'io.spring.dependency-management' version '1.1.4' + id 'checkstyle' +} + +group = 'com' +version = '1.0.0' + +java { + sourceCompatibility = '17' +} +checkstyle { + configDirectory = file("$rootProject.projectDir/../../config/checkstyle") + toolVersion '10.12.7' +} +checkstyleMain { + source = 'src/main/java' +} +checkstyleTest { + source = 'src/test/java' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.h2database:h2' + implementation 'org.postgresql:postgresql:42.7.3' + + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation group: 'org.modelmapper', name: 'modelmapper', version: '3.1.1' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + implementation 'org.springframework.boot:spring-boot-starter-validation' +} + +tasks.named('bootBuildImage') { + builder = 'paketobuildpacks/builder-jammy-base:latest' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/backend/sportsmatch/docker/docker-compose.yml b/backend/sportsmatch/docker/docker-compose.yml new file mode 100644 index 00000000..30b9ea25 --- /dev/null +++ b/backend/sportsmatch/docker/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + + crdb: + container_name: crdb + hostname: crdb + image: cockroachdb/cockroach:latest + command: start-single-node --cluster-name=example-single-node --logtostderr=WARNING --log-file-verbosity=WARNING --insecure + ports: + - "26257:26257" + - "8888:8080" + volumes: + - roach-single:/cockroach/cockroach-data + +volumes: + roach-single: \ No newline at end of file diff --git a/backend/sportsmatch/gradle/wrapper/gradle-wrapper.jar b/backend/sportsmatch/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..d64cd491 Binary files /dev/null and b/backend/sportsmatch/gradle/wrapper/gradle-wrapper.jar differ diff --git a/backend/sportsmatch/gradle/wrapper/gradle-wrapper.properties b/backend/sportsmatch/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/backend/sportsmatch/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/sportsmatch/gradlew b/backend/sportsmatch/gradlew new file mode 100644 index 00000000..1aa94a42 --- /dev/null +++ b/backend/sportsmatch/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +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 + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/sportsmatch/gradlew.bat b/backend/sportsmatch/gradlew.bat new file mode 100644 index 00000000..93e3f59f --- /dev/null +++ b/backend/sportsmatch/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +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" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/backend/sportsmatch/settings.gradle b/backend/sportsmatch/settings.gradle new file mode 100644 index 00000000..7afa46c6 --- /dev/null +++ b/backend/sportsmatch/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'sportsmatch' diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/SportsmatchApplication.java b/backend/sportsmatch/src/main/java/com/sportsmatch/SportsmatchApplication.java new file mode 100644 index 00000000..0cbdadad --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/SportsmatchApplication.java @@ -0,0 +1,385 @@ +package com.sportsmatch; + +import com.sportsmatch.configs.InitProperties; +import com.sportsmatch.models.*; +import com.sportsmatch.repositories.*; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Random; +import lombok.AllArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@AllArgsConstructor +@SpringBootApplication +@EnableWebMvc +@ConfigurationPropertiesScan("com.sportsmatch.configs") +public class SportsmatchApplication implements CommandLineRunner { + + private final UserRepository userRepository; + private final SportRepository sportRepository; + private final PlaceRepository placeRepository; + private final SportUserRepository sportUserRepository; + private final EventPlayerRepository eventPlayerRepository; + private final EventRepository eventRepository; + private final PasswordEncoder passwordEncoder; + private final InitProperties initProperties; + + private final RatingRepository ratingRepository; + private final UserEventRatingRepository userEventRatingRepository; + + public static void main(String[] args) { + SpringApplication.run(SportsmatchApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + if (initProperties.getDatabaseInit()) { + addData(); + } + } + + public void addData() { + List places = addPlaces(); + List sports = addSports(); + List users = addUsers(); + List sportUsers = addSportUsers(sports, users); + List events = addEvents(sports, places); + List eventPlayers = addEventPlayers(events, users); + List ratings = addRatings(); + addUserEventRating(ratings, users, events); + } + + public void addUserEventRating(List ratings, List users, List events) { + UserEventRating uer1 = new UserEventRating(); + uer1.setEvent(events.get(0)); + uer1.setPlayer(users.get(9)); + uer1.setOpponent(users.get(0)); + uer1.setUserRating(ratings.get(0)); + UserEventRating uer2 = new UserEventRating(); + uer2.setEvent(events.get(5)); + uer2.setPlayer(users.get(9)); + uer2.setOpponent(users.get(0)); + uer2.setUserRating(ratings.get(1)); + UserEventRating uer3 = new UserEventRating(); + uer3.setEvent(events.get(6)); + uer3.setPlayer(users.get(9)); + uer3.setOpponent(users.get(0)); + uer3.setUserRating(ratings.get(2)); + UserEventRating uer4 = new UserEventRating(); + uer4.setEvent(events.get(7)); + uer4.setPlayer(users.get(0)); + uer4.setOpponent(users.get(9)); + uer4.setUserRating(ratings.get(3)); + UserEventRating uer5 = new UserEventRating(); + uer5.setEvent(events.get(0)); + uer5.setPlayer(users.get(0)); + uer5.setOpponent(users.get(9)); + uer5.setUserRating(ratings.get(4)); + UserEventRating uer6 = new UserEventRating(); + uer6.setEvent(events.get(5)); + uer6.setPlayer(users.get(0)); + uer6.setOpponent(users.get(9)); + uer6.setUserRating(ratings.get(5)); + UserEventRating uer7 = new UserEventRating(); + uer7.setEvent(events.get(6)); + uer7.setPlayer(users.get(0)); + uer7.setOpponent(users.get(9)); + uer7.setUserRating(ratings.get(6)); + UserEventRating uer8 = new UserEventRating(); + uer8.setEvent(events.get(7)); + uer8.setPlayer(users.get(9)); + uer8.setOpponent(users.get(0)); + uer8.setUserRating(ratings.get(7)); + userEventRatingRepository.save(uer1); + userEventRatingRepository.save(uer2); + userEventRatingRepository.save(uer3); + userEventRatingRepository.save(uer4); + userEventRatingRepository.save(uer5); + userEventRatingRepository.save(uer6); + userEventRatingRepository.save(uer7); + userEventRatingRepository.save(uer8); + + for (int i = 8; i < 58; i++) { + UserEventRating uer = + UserEventRating.builder() + .event(events.get(7)) + .player(users.get(9)) + .opponent(users.get(0)) + .userRating(ratings.get(i)) + .build(); + userEventRatingRepository.save(uer); + } + } + + public List addRatings() { + Rating rating1 = new Rating(); + rating1.setStarRating(5); + rating1.setTextRating("great"); + ratingRepository.save(rating1); + Rating rating2 = new Rating(); + rating2.setStarRating(4); + rating2.setTextRating("wonderful"); + ratingRepository.save(rating2); + Rating rating3 = new Rating(); + rating3.setStarRating(2); + rating3.setTextRating("could have been better"); + ratingRepository.save(rating3); + Rating rating4 = new Rating(); + rating4.setStarRating(2); + rating4.setTextRating("not good"); + ratingRepository.save(rating4); + Rating rating5 = new Rating(); + rating5.setStarRating(4); + rating5.setTextRating("perfect"); + ratingRepository.save(rating5); + Rating rating6 = new Rating(); + rating6.setStarRating(4); + rating6.setTextRating("good"); + ratingRepository.save(rating6); + Rating rating7 = new Rating(); + rating7.setStarRating(1); + rating7.setTextRating("bad"); + ratingRepository.save(rating7); + Rating rating8 = new Rating(); + rating8.setStarRating(2); + rating8.setTextRating("not so good"); + ratingRepository.save(rating8); + + Random rand = new Random(); + for (int i = 0; i < 50; i++) { + int randomNumber = rand.nextInt(5 - 1 + 1) + 1; + Rating rating = + Rating.builder().starRating(randomNumber).textRating("test rating " + i).build(); + ratingRepository.save(rating); + } + return ratingRepository.findAll(); + } + + public List addEventPlayers(List events, List users) { + eventPlayerRepository.save(new EventPlayer(1, 3, users.get(0), events.get(0))); + eventPlayerRepository.save(new EventPlayer(3, 1, users.get(9), events.get(0))); + eventPlayerRepository.save(new EventPlayer(3, 2, users.get(1), events.get(1))); + eventPlayerRepository.save(new EventPlayer(3, 1, users.get(8), events.get(1))); + eventPlayerRepository.save(new EventPlayer(2, 2, users.get(2), events.get(2))); + eventPlayerRepository.save(new EventPlayer(2, 2, users.get(7), events.get(2))); + eventPlayerRepository.save(new EventPlayer(0, 1, users.get(3), events.get(3))); + eventPlayerRepository.save(new EventPlayer(1, 0, users.get(6), events.get(3))); + eventPlayerRepository.save(new EventPlayer(2, 1, users.get(4), events.get(4))); + eventPlayerRepository.save(new EventPlayer(1, 2, users.get(5), events.get(4))); + eventPlayerRepository.save(new EventPlayer(1, 3, users.get(0), events.get(5))); + eventPlayerRepository.save(new EventPlayer(3, 1, users.get(9), events.get(5))); + eventPlayerRepository.save(new EventPlayer(1, 3, users.get(0), events.get(6))); + eventPlayerRepository.save(new EventPlayer(3, 1, users.get(9), events.get(6))); + eventPlayerRepository.save(new EventPlayer(1, 3, users.get(0), events.get(7))); + eventPlayerRepository.save(new EventPlayer(3, 1, users.get(9), events.get(7))); + return eventPlayerRepository.findAll(); + } + + public List addEvents(List sports, List places) { + eventRepository.save( + new Event( + LocalDateTime.of(2024, 5, 1, 14, 30), + LocalDateTime.of(2024, 5, 1, 16, 30), + "Prague, Stadium A", + 1200, + 2000, + "Badminton match", + sports.get(0), + places.get(0))); + eventRepository.save( + new Event( + LocalDateTime.of(2024, 7, 10, 18, 0), + LocalDateTime.of(2024, 7, 10, 20, 0), + "Brno, Gym", + 1500, + 2500, + "Boxing event", + sports.get(1), + places.get(1))); + eventRepository.save( + new Event( + LocalDateTime.of(2024, 8, 5, 9, 0), + LocalDateTime.of(2024, 8, 5, 11, 0), + "Prague", + 800, + 1600, + "Table Tennis for beginners", + sports.get(2), + places.get(2))); + eventRepository.save( + new Event( + LocalDateTime.of(2024, 9, 20, 15, 0), + LocalDateTime.of(2024, 9, 20, 17, 0), + "Prague, Squash Centre", + 1400, + 2200, + "Squash challenge", + sports.get(3), + places.get(3))); + eventRepository.save( + new Event( + LocalDateTime.of(2024, 6, 15, 10, 0), + LocalDateTime.of(2024, 6, 15, 12, 0), + "Prague Stvanice", + 1000, + 1800, + "Tennis Open 1", + sports.get(4), + places.get(4))); + eventRepository.save( + new Event( + LocalDateTime.of(2023, 6, 15, 10, 0), + LocalDateTime.of(2023, 6, 15, 12, 0), + "Prague Stvanice", + 1000, + 1800, + "Tennis Open 2", + sports.get(4), + places.get(4))); + eventRepository.save( + new Event( + LocalDateTime.of(2023, 6, 15, 10, 0), + LocalDateTime.of(2023, 6, 15, 12, 0), + "Prague Stvanice", + 1000, + 1800, + "Tennis Open 3", + sports.get(4), + places.get(4))); + eventRepository.save( + new Event( + LocalDateTime.of(2023, 6, 15, 10, 0), + LocalDateTime.of(2023, 6, 15, 12, 0), + "Prague Stvanice", + 1000, + 1800, + "Tennis Open 4", + sports.get(4), + places.get(4))); + return eventRepository.findAll(); + } + + public List addSportUsers(List sports, List users) { + sportUserRepository.save(new SportUser(users.get(0), sports.get(0))); + sportUserRepository.save(new SportUser(users.get(9), sports.get(0))); + sportUserRepository.save(new SportUser(users.get(1), sports.get(1))); + sportUserRepository.save(new SportUser(users.get(8), sports.get(1))); + sportUserRepository.save(new SportUser(users.get(2), sports.get(2))); + sportUserRepository.save(new SportUser(users.get(7), sports.get(2))); + // sportUserRepository.save(new SportUser(users.get( dev3), sports.get(3))); + sportUserRepository.save(new SportUser(users.get(6), sports.get(3))); + sportUserRepository.save(new SportUser(users.get(4), sports.get(4))); + sportUserRepository.save(new SportUser(users.get(5), sports.get(4))); + return sportUserRepository.findAll(); + } + + public List addUsers() { + userRepository.save( + new User( + "john.doe@example.com", + passwordEncoder.encode("pass123"), + "johndoe87", + Gender.MALE, + LocalDate.of(1990, 5, 15))); + userRepository.save( + new User( + "alice.smith@example.com", + passwordEncoder.encode("securePass"), + "alice.smith", + Gender.FEMALE, + LocalDate.of(1985, 11, 22))); + userRepository.save( + new User( + "bob.jones@example.com", + passwordEncoder.encode("b0bPass"), + "bobjones22", + Gender.MALE, + LocalDate.of(1992, 8, 10))); + userRepository.save( + new User( + "emily.white@example.com", + passwordEncoder.encode("emilyPass"), + "em_white", + Gender.FEMALE, + LocalDate.of(1988, 4, 3))); + userRepository.save( + new User( + "mike.jackson@example.com", + passwordEncoder.encode("mjPass2020"), + "mikej", + Gender.MALE, + LocalDate.of(1995, 12, 18))); + userRepository.save( + new User( + "lisa.martin@example.com", + passwordEncoder.encode("lisaPass123"), + "lisa_m", + Gender.FEMALE, + LocalDate.of(1998, 7, 25))); + userRepository.save( + new User( + "chris.brown@example.com", + passwordEncoder.encode("cbrownPass"), + "chris_b", + Gender.MALE, + LocalDate.of(1983, 9, 14))); + userRepository.save( + new User( + "sarah.green@example.com", + passwordEncoder.encode("greenSarah"), + "s_green", + Gender.FEMALE, + LocalDate.of(1993, 2, 9))); + userRepository.save( + new User( + "ryan.miller@example.com", + passwordEncoder.encode("ryanPass456"), + "ryanm", + Gender.MALE, + LocalDate.of(1987, 6, 30))); + userRepository.save( + new User( + "jessica.ward@example.com", + passwordEncoder.encode("jessWard789"), + "jess_ward", + Gender.FEMALE, + LocalDate.of(1991, 3, 12))); + + return userRepository.findAll(); + } + + public List addSports() { + String badmintonEmoji = "\uD83C\uDFF8"; // badminton racket emoji + String boxingEmoji = "\uD83E\uDD4A"; // boxing glove emoji + String tableTennisEmoji = "\uD83C\uDFD3"; // table tennis racket emoji + String tennisEmoji = "\uD83C\uDFBE"; // tennis racket emoji + String squashEmoji = "\uD83E\uDD4E"; // green softball emoji + + sportRepository.save( + new Sport("Badminton", badmintonEmoji, "./assets/sport-component-badminton.png")); + sportRepository.save(new Sport("Boxing", boxingEmoji, "./assets/sport-component-boxing.png")); + sportRepository.save( + new Sport("Table Tennis", tableTennisEmoji, "./assets/sport-component-table-tennis.png")); + sportRepository.save(new Sport("Squash", squashEmoji, "./assets/sport-component-squash.png")); + sportRepository.save(new Sport("Tennis", tennisEmoji, "./assets/sport-component-tennis.png")); + + return sportRepository.findAll(); + } + + public List addPlaces() { + placeRepository.save(new Place("Kanizsa Aréna", "Hungary", 46.4618, 17.0102)); + placeRepository.save(new Place("AT&T Stadium Box", "USA", 32.7686, -96.711308)); + placeRepository.save(new Place("Parc des Princes", "France", 48.8413634, 2.2530693)); + placeRepository.save(new Place("San Siro", "Italy", 45.480290, 9.123080)); + placeRepository.save(new Place("Burj Al Arab Helipad Tennis", "Dubai", 25.1411934, 55.1855516)); + placeRepository.save(new Place("Old Trafford", "England", 53.464560, -2.289720)); + + return placeRepository.findAll(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/auth/AuthConfig.java b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/AuthConfig.java new file mode 100644 index 00000000..c6a79cc7 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/AuthConfig.java @@ -0,0 +1,48 @@ +package com.sportsmatch.auth; + +import com.sportsmatch.repositories.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@RequiredArgsConstructor +public class AuthConfig { + + private final UserRepository userRepository; + + @Bean + public UserDetailsService userDetailsService() { + return username -> + userRepository + .findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService()); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) + throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/auth/AuthService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/AuthService.java new file mode 100644 index 00000000..5b4cb46e --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/AuthService.java @@ -0,0 +1,42 @@ +package com.sportsmatch.auth; + +import com.sportsmatch.dtos.AuthRequestDTO; +import com.sportsmatch.dtos.AuthResponseDTO; +import com.sportsmatch.mappers.UserMapper; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final UserMapper userMapper; + private final JwtService jwtService; + private final UserRepository userRepository; + private final AuthenticationManager authenticationManager; + + public AuthResponseDTO register(AuthRequestDTO authRequestDTO) { + User user = userMapper.registerToUser(authRequestDTO); + if (userRepository.existsByEmail(user.getEmail())) { + throw new ResponseStatusException(HttpStatus.CONFLICT); + } + userRepository.save(user); + String jwtToken = jwtService.generateToken(user); + return AuthResponseDTO.builder().token(jwtToken).build(); + } + + public AuthResponseDTO login(AuthRequestDTO authRequestDTO) { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + authRequestDTO.getEmail(), authRequestDTO.getPassword())); + User user = userRepository.findByEmail(authRequestDTO.getEmail()).orElseThrow(); + String jwtToken = jwtService.generateToken(user); + return AuthResponseDTO.builder().token(jwtToken).build(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/auth/JwtAuthFilter.java b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/JwtAuthFilter.java new file mode 100644 index 00000000..3b5fac2a --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/JwtAuthFilter.java @@ -0,0 +1,115 @@ +package com.sportsmatch.auth; + +import com.sportsmatch.repositories.TokenRepository; +import io.jsonwebtoken.JwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import static com.sportsmatch.auth.SecurityConfig.API_WHITE_LIST_URL; + +@Component +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + private final TokenRepository tokenRepository; + + private final List protectedPaths = + List.of(new AntPathRequestMatcher("/api/v1/**")); + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) + throws ServletException, IOException { + + final String authHeader = request.getHeader("Authorization"); + boolean requiresAuthentication = protectedPaths.stream().anyMatch(m -> m.matches(request)); + boolean isWhitelisted = + Arrays.stream(API_WHITE_LIST_URL) + .anyMatch(path -> new AntPathRequestMatcher(path).matches(request)); + + // Allow unsecured requests to pass through + if (!requiresAuthentication || isWhitelisted) { + filterChain.doFilter(request, response); + return; + } + + if (isBearerTokenNotPresent(authHeader)) { + sendUnauthorizedError(response, "Token not present"); + return; + } + + try { + handleJwtAuthentication(authHeader, request, response); + } catch (JwtException e) { + sendUnauthorizedError(response, "Invalid token"); + return; + } + + filterChain.doFilter(request, response); + } + + private void handleJwtAuthentication( + String authHeader, HttpServletRequest request, HttpServletResponse response) + throws IOException { + final String jwt = authHeader.substring(7); + final String userEmail = jwtService.extractUserName(jwt); + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); + + if (!isAuthenticationNeeded(userEmail)) { + // No authentication needed for this request + return; + } + + if (jwtService.isTokenValid(jwt, userDetails)) { + if (!tokenRepository.existsByToken(jwt)) { + updateSecurityContext(request, userDetails); + } + } else { + sendUnauthorizedError(response, "Invalid or expired token"); + } + } + + public boolean isBearerTokenNotPresent(String authHeader) { + if (authHeader == null) { + return true; + } + String[] tokenParts = authHeader.split(" "); + return !authHeader.startsWith("Bearer ") || tokenParts.length != 2; + } + + private boolean isAuthenticationNeeded(String userEmail) { + return userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null; + } + + private void updateSecurityContext(HttpServletRequest request, UserDetails userDetails) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + + private void sendUnauthorizedError(HttpServletResponse response, String errorMessage) + throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, errorMessage); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/auth/JwtService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/JwtService.java new file mode 100644 index 00000000..f1b0bcdb --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/JwtService.java @@ -0,0 +1,70 @@ +package com.sportsmatch.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +@Service +public class JwtService { + + @Value("${app.sportsmingle.jwt.secret}") + private String SECRET_KEY; + + public String extractUserName(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + public String generateToken(UserDetails userDetails) { + return generateToken(new HashMap<>(), userDetails); + } + + public String generateToken(Map extraClaims, UserDetails userDetails) { + return Jwts.builder() + .claims(extraClaims) + .subject(userDetails.getUsername()) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) + .signWith(getVerificationKey(), Jwts.SIG.HS256) + .compact(); + } + + public boolean isTokenValid(String token, UserDetails userDetails) { + final String userName = extractUserName(token); + return (userName.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } + + private boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser() + .verifyWith(getVerificationKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + private SecretKey getVerificationKey() { + byte[] keyBytes = Decoders.BASE64URL.decode(SECRET_KEY); + return Keys.hmacShaKeyFor(keyBytes); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/auth/LogoutService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/LogoutService.java new file mode 100644 index 00000000..16c29dc4 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/LogoutService.java @@ -0,0 +1,46 @@ +package com.sportsmatch.auth; + +import com.sportsmatch.models.Token; +import com.sportsmatch.models.TokenType; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.TokenRepository; +import com.sportsmatch.repositories.UserRepository; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LogoutService implements LogoutHandler { + + private final JwtAuthFilter authFilter; + private final TokenRepository tokenRepository; + private final UserRepository userRepository; + private final JwtService jwtService; + + @Override + public void logout( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + + final String authHeader = request.getHeader("Authorization"); + final String jwt = authHeader.substring(7); + final String userEmail = jwtService.extractUserName(jwt); + Optional user = userRepository.findByEmail(userEmail); + + if (authFilter.isBearerTokenNotPresent(authHeader) || user.isEmpty()) { + return; + } + + tokenRepository.save( + Token.builder() + .token(jwt) + .user(user.get()) + .tokenType(TokenType.BEARER) + .isValid(false) + .build()); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/auth/SecurityConfig.java b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/SecurityConfig.java new file mode 100644 index 00000000..36af3498 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/auth/SecurityConfig.java @@ -0,0 +1,77 @@ +package com.sportsmatch.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + @Value("${app.sportsmingle.frontend.url}") + private String frontendUrl; + + static final String[] API_WHITE_LIST_URL = { + "/api/v1/auth/login", + "/api/v1/auth/register", + "/api/v1/places/search", + "/api/v1/event/nearby", + "/api/v1/sports/all" + }; + + private final JwtAuthFilter jwtAuthFilter; + private final AuthenticationProvider authenticationProvider; + private final LogoutHandler logoutHandler; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) + .headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + .authorizeHttpRequests( + r -> { + r.requestMatchers(API_WHITE_LIST_URL).permitAll(); + r.requestMatchers("/api/v1/**").authenticated(); + r.anyRequest().permitAll(); + }) + .sessionManagement(s -> s.sessionCreationPolicy(STATELESS)) + .authenticationProvider(authenticationProvider) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(corsFilter(), JwtAuthFilter.class) + .logout( + l -> + l.logoutUrl("/api/v1/auth/logout") + .addLogoutHandler(logoutHandler) + .logoutSuccessHandler( + ((request, response, authentication) -> + SecurityContextHolder.clearContext()))); + return http.build(); + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOrigin(frontendUrl); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + source.registerCorsConfiguration("/api/v1/**", config); + return new CorsFilter(source); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/configs/GlobalExceptionHandler.java b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/GlobalExceptionHandler.java new file mode 100644 index 00000000..e08e8da1 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/GlobalExceptionHandler.java @@ -0,0 +1,33 @@ +package com.sportsmatch.configs; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import javax.security.sasl.AuthenticationException; + +@ControllerAdvice +public class GlobalExceptionHandler { + + // handle request body + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException( + HttpMessageNotReadableException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + + // handle parameter with @Valid + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException( + MethodArgumentNotValidException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException(AuthenticationException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/configs/InitProperties.java b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/InitProperties.java new file mode 100644 index 00000000..d23338b2 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/InitProperties.java @@ -0,0 +1,13 @@ +package com.sportsmatch.configs; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@AllArgsConstructor +@Getter +@ConfigurationProperties(prefix = "app.sportsmingle.initialization") +public class InitProperties { + + private Boolean databaseInit; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/configs/ModelMapperConfig.java b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/ModelMapperConfig.java new file mode 100644 index 00000000..1cccb729 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/ModelMapperConfig.java @@ -0,0 +1,14 @@ +package com.sportsmatch.configs; + +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ModelMapperConfig { + + @Bean + public ModelMapper modelMapper() { + return new ModelMapper(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/configs/OpenApiConfig.java b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/OpenApiConfig.java new file mode 100644 index 00000000..dd96d9e8 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/configs/OpenApiConfig.java @@ -0,0 +1,34 @@ +package com.sportsmatch.configs; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Contact; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import io.swagger.v3.oas.annotations.servers.Server; +import org.springframework.context.annotation.Configuration; + +@Configuration +@OpenAPIDefinition( + info = + @Info( + contact = + @Contact( + name = "Sportsmingle", + email = "hello@sportmingle.com", + url = "sportsmingle.com"), + description = "Sport event scheduling and matchmaking", + title = "Sportsmingle", + version = "v1"), + servers = {@Server(description = "Local ENV", url = "http://localhost:8080")}, + security = {@SecurityRequirement(name = "bearerAuth")}) +@SecurityScheme( + name = "bearerAuth", + description = "JWT auth description", + scheme = "bearer", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + in = SecuritySchemeIn.HEADER) +public class OpenApiConfig {} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/AuthController.java b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/AuthController.java new file mode 100644 index 00000000..ade47149 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/AuthController.java @@ -0,0 +1,61 @@ +package com.sportsmatch.controllers; + +import com.sportsmatch.auth.AuthService; +import com.sportsmatch.dtos.AuthRequestDTO; +import com.sportsmatch.dtos.UserDTO; +import com.sportsmatch.services.UserService; +import com.sportsmatch.services.ValidationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@AllArgsConstructor +@RequestMapping("/api/v1/auth") +public class AuthController { + + private final AuthService authService; + private final ValidationService validationService; + private final UserService userService; + + @PostMapping("/register") + @Tag(name = "Register") + @Operation( + summary = "Register new user", + description = "Register a new user by providing their email and username.") + public ResponseEntity register( + @RequestBody @Valid AuthRequestDTO authRequestDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest().body(validationService.getAllErrors(bindingResult)); + } + try { + return ResponseEntity.ok(authService.register(authRequestDTO)); + } catch (ResponseStatusException e) { + return ResponseEntity.status(e.getStatusCode()).build(); + } + } + + @PostMapping("/login") + @Tag(name = "Login") + @Operation( + summary = "Login user", + description = "Login a user by providing their email and username.") + public ResponseEntity login( + @RequestBody @Valid AuthRequestDTO authRequestDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest().body(validationService.getAllErrors(bindingResult)); + } + return ResponseEntity.ok(authService.login(authRequestDTO)); + } + + @GetMapping("/me") + @Tag(name = "ex.secured endpoint") + public UserDTO getUserMainPage() { + return userService.getMyRank(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/EventsController.java b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/EventsController.java new file mode 100644 index 00000000..2bc8aeca --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/EventsController.java @@ -0,0 +1,99 @@ +package com.sportsmatch.controllers; + +import com.sportsmatch.dtos.EventDTO; +import com.sportsmatch.dtos.EventHistoryDTO; +import com.sportsmatch.dtos.HostEventDTO; +import com.sportsmatch.dtos.RequestEventDTO; +import com.sportsmatch.models.Event; +import com.sportsmatch.services.EventService; +import jakarta.validation.Valid; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/event") +public class EventsController { + + private final EventService eventService; + + public EventsController(EventService eventService) { + this.eventService = eventService; + } + + @GetMapping("/{id}") + public ResponseEntity getEvent(@PathVariable("id") Long id) { + EventDTO eventDTO = eventService.getEventDTObyEventId(id); + return ResponseEntity.ok().body(eventDTO); + } + + @PostMapping("") + public ResponseEntity addEvent(@RequestBody @Valid HostEventDTO hostEventDTO) { + Event newEvent = eventService.createEvent(hostEventDTO); + return ResponseEntity.status(HttpStatus.CREATED) + .body(eventService.getEventDTObyEventId(newEvent.getId())); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteEvent(@PathVariable("id") Long id) { + Event eventById = eventService.getEventById(id); + eventService.deleteEventFromDatabase(eventById); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + @GetMapping("/upcoming-events") + public ResponseEntity getUpcomingEvents(@RequestBody List sportsIds) { + List listOfEvents = eventService.getEventsBySports(sportsIds); + return ResponseEntity.ok().body(listOfEvents); + } + + /** + * This endpoint returns the history of the finished events by the logged-in user. + * + * @param pageable it contains the page and size for pagination + * @return a list of finished EventHistoryDTO of the logged-in user + */ + @GetMapping("/event-history") + public List getEventsHistory(@ParameterObject final Pageable pageable) { + return eventService.getEventsHistory(pageable); + } + + /** + * This endpoint returns list of Events sorted by distance from the given location. User can + * filter by sports. + * + * @param requestEventDTO it contains longitude and latitude and a list of sports for filter if + * given + * @param pageable it contains the page and size for pagination + * @return a list of Events sorted by distance from the given location. User can filter by sports. + */ + @GetMapping("/nearby") + public List getNearbyEvents( + @ParameterObject RequestEventDTO requestEventDTO, @ParameterObject final Pageable pageable) { + return eventService.getNearbyEvents(requestEventDTO, pageable); + } + + @PostMapping("/{id}/join") + public ResponseEntity joinEvent(@PathVariable("id") Long id) { + try { + eventService.joinEvent(id); + return ResponseEntity.ok().build(); + } catch (Exception e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + /** + * This endpoint returns the upcoming matches of the logged-in user. + * + * @return a list of logged-in user's upcoming EventDTOs ordered by date ascending + */ + @GetMapping("/upcoming-matches") + public List getUpcomingMatches() { + return eventService.getUsersUpcomingEvents(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/PlaceController.java b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/PlaceController.java new file mode 100644 index 00000000..ebcd220b --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/PlaceController.java @@ -0,0 +1,41 @@ +package com.sportsmatch.controllers; + +import com.sportsmatch.dtos.PlaceDTO; +import com.sportsmatch.services.PlaceService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/places") +public class PlaceController { + + private final PlaceService placeService; + + /** + * Endpoint to add new Place based on provided PlaceDTO only authenticated users. + * + * @param placeDTO object containing the data about the new Place. + * @return ResponseEntity with HTTP status and some meaningful message. + */ + @PostMapping("/add") + public ResponseEntity addNewPlace(@RequestBody PlaceDTO placeDTO) { + return placeService.addNewPlace(placeDTO); + } + + + /** + * Endpoint allow user to search for places based on the provided name. If no name is provided, then returns all places. + * No authenticated needed. + * + * @param name query String for searching places. + * @return a list of PlaceDTO representing the places that match the provided name or all places if no name is provided. + */ + @GetMapping("/search") + public List searchPlaces(@RequestParam(required = false) String name) { + return placeService.searchPlaces(name); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/RatingController.java b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/RatingController.java new file mode 100644 index 00000000..7b24e1dc --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/RatingController.java @@ -0,0 +1,63 @@ +package com.sportsmatch.controllers; + +import com.sportsmatch.dtos.RatingDTO; +import com.sportsmatch.dtos.UserRatingDTO; +import com.sportsmatch.dtos.UserRatingStatsDTO; +import com.sportsmatch.services.RatingService; +import com.sportsmatch.services.ValidationService; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/rating") +public class RatingController { + + private final RatingService ratingService; + private final ValidationService validationService; + + @PostMapping("/add") + public ResponseEntity addRating( + @RequestBody @Valid RatingDTO ratingDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest().body(validationService.getAllErrors(bindingResult)); + } + try { + ratingService.addRating(ratingDTO); + return ResponseEntity.ok().build(); + } catch (ResponseStatusException e) { + return ResponseEntity.status(e.getStatusCode()).build(); + } + } + + @GetMapping("/check") + public ResponseEntity checkRating() { + return ResponseEntity.ok().body(ratingService.findUnratedEvents()); + } + + @GetMapping("/{id}/summary") + @ApiResponse(content = @Content(schema = @Schema(implementation = UserRatingStatsDTO.class))) + public ResponseEntity getSummary(@PathVariable Long id) { + try { + UserRatingStatsDTO summary = ratingService.getUserRatingStats(id); + return ResponseEntity.ok().body(summary); + } catch (Exception e) { + return ResponseEntity.badRequest().build(); + } + } + + @GetMapping("/{id}/all") + public List getAllByUser(@PathVariable Long id, @ParameterObject Pageable pageable) { + return ratingService.getAllUserRatings(id, pageable); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/SportController.java b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/SportController.java new file mode 100644 index 00000000..98299148 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/SportController.java @@ -0,0 +1,31 @@ +package com.sportsmatch.controllers; + +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.services.SportService; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/v1/sports") +public class SportController { + + private final SportService sportService; + + /** + * This endpoint returns paginated list of SportDto. + * + * @param pageable contains tha page and size value + * @return paginated list of SportDTO + */ + @GetMapping("/all") + public List getSports(@ParameterObject final Pageable pageable) { + return sportService.getAllSports(pageable); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/UserController.java b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/UserController.java new file mode 100644 index 00000000..c795511e --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/controllers/UserController.java @@ -0,0 +1,43 @@ +package com.sportsmatch.controllers; + +import com.sportsmatch.dtos.UserInfoDTO; +import com.sportsmatch.services.UserService; +import com.sportsmatch.services.ValidationService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/user") +public class UserController { + + private final ValidationService validationService; + private final UserService userService; + + @GetMapping("/get/{id}") + public ResponseEntity getUser(@PathVariable Long id) { + try { + return ResponseEntity.ok().body(userService.getUserById(id)); + } catch (ResponseStatusException e) { + return ResponseEntity.status(e.getStatusCode()).build(); + } + } + + @PostMapping("/update") + public ResponseEntity updateInfo( + @RequestBody @Valid UserInfoDTO userInfoDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest().body(validationService.getAllErrors(bindingResult)); + } + try { + userService.updateUserInfo(userInfoDTO); + return ResponseEntity.ok().build(); + } catch (ResponseStatusException e) { + return ResponseEntity.status(e.getStatusCode()).build(); + } + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/AuthRequestDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/AuthRequestDTO.java new file mode 100644 index 00000000..714711d0 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/AuthRequestDTO.java @@ -0,0 +1,23 @@ +package com.sportsmatch.dtos; + +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.*; +import lombok.*; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AuthRequestDTO { + + @NotNull(message = "Email address is required.") + @Email(message = "Please provide a valid email address") + @NotBlank(message = "email cannot be blank") + private String email; + + @NotBlank(message = "Password cannot be blank") + @NotNull(message = "Please provide a password") + private String password; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/AuthResponseDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/AuthResponseDTO.java new file mode 100644 index 00000000..0ad2bb55 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/AuthResponseDTO.java @@ -0,0 +1,15 @@ +package com.sportsmatch.dtos; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AuthResponseDTO { + + private String token; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/EventDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/EventDTO.java new file mode 100644 index 00000000..e7d376e4 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/EventDTO.java @@ -0,0 +1,41 @@ +package com.sportsmatch.dtos; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EventDTO { + + private Long id; + + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy HH:mm") + private LocalDateTime dateStart; + @NotNull + @JsonFormat(pattern = "dd.MM.yyyy HH:mm") + private LocalDateTime dateEnd; + @NotNull + private Integer minElo; + @NotNull + private Integer maxElo; + @NotBlank + private String title; + + private Long player1Id; + private Long player2Id; + @NotBlank private String sport; + + private String player1Name; + private String player2Name; + + @NotNull + private PlaceDTO placeDTO; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/EventHistoryDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/EventHistoryDTO.java new file mode 100644 index 00000000..8e0385a4 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/EventHistoryDTO.java @@ -0,0 +1,21 @@ +package com.sportsmatch.dtos; + +import com.sportsmatch.models.EventStatusOptions; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EventHistoryDTO { + + private Integer userScore; + private Integer opponentScore; + private UserDTO opponent; + private LocalDateTime dateOfTheMatch; + private EventStatusOptions status; + +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/HostEventDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/HostEventDTO.java new file mode 100644 index 00000000..05aad09a --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/HostEventDTO.java @@ -0,0 +1,30 @@ +package com.sportsmatch.dtos; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HostEventDTO { + + @NotNull + private LocalDateTime dateStart; + @NotNull + private LocalDateTime dateEnd; + @NotNull + private Integer minElo; + @NotNull + private Integer maxElo; + @NotBlank + private String title; + @NotBlank + private String sport; + @NotNull + private Long locationId; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/PlaceDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/PlaceDTO.java new file mode 100644 index 00000000..7f3669ec --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/PlaceDTO.java @@ -0,0 +1,29 @@ +package com.sportsmatch.dtos; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PlaceDTO { + + private Long id; + + @NotBlank + private String name; + + @NotBlank + private String address; + + @NotNull + private Double latitude; + + @NotNull + private Double longitude; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RatingDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RatingDTO.java new file mode 100644 index 00000000..7bcd332b --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RatingDTO.java @@ -0,0 +1,38 @@ +package com.sportsmatch.dtos; + +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RatingDTO { + + private String userTextRating; + + @NotNull(message = "User star cannot be null") + @Min(value = 0, message = "User star rating must be at least 0") + @Max(value = 5, message = "User star rating must be at most 5") + private Integer userStarRating; + + @NotNull(message = "Event star cannot be null") + @Min(value = 0, message = "Event star rating must be at least 0") + @Max(value = 5, message = "Event star rating must be at most 5") + private Integer eventStarRating; + + @NotNull(message = "My score cannot be null") + @Min(value = 0, message = "My score must be at least 0") + private Integer myScore; + + @NotNull(message = "Opponent score cannot be null") + @Min(value = 0, message = "Opponent score must be at least 0") + private Integer opponentScore; + + private Long eventId; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java new file mode 100644 index 00000000..40ea6841 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/RequestEventDTO.java @@ -0,0 +1,23 @@ +package com.sportsmatch.dtos; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.ArrayList; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class RequestEventDTO { + + private List sportsName = new ArrayList<>(); + private double longitude; + private double latitude; + private String placeName; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/SportDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/SportDTO.java new file mode 100644 index 00000000..c00266f4 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/SportDTO.java @@ -0,0 +1,24 @@ +package com.sportsmatch.dtos; + +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +public class SportDTO { + public String name; + + public String emoji; + + public String backgroundUImageURL; + public Long id; + + public SportDTO(String name, String emoji, String backgroundUImageURL, Long id) { + this.name = name; + this.emoji = emoji; + this.backgroundUImageURL = backgroundUImageURL; + this.id = id; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserDTO.java new file mode 100644 index 00000000..9ccad33e --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserDTO.java @@ -0,0 +1,19 @@ +package com.sportsmatch.dtos; + +import java.util.List; +import lombok.*; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class UserDTO { + + private Long id; + private String name; + private Integer elo; + private Integer win; + private Integer loss; + private List sports; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserInfoDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserInfoDTO.java new file mode 100644 index 00000000..45bf4e85 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserInfoDTO.java @@ -0,0 +1,32 @@ +package com.sportsmatch.dtos; + +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserInfoDTO { + + @NotNull(message = "Username cannot be null.") + private String userName; + + @Pattern( + regexp = "\\d{2}-\\d{2}-\\d{4}", + message = "Date of birth must be in the format dd-MM-yyyy") + @NotNull + private String dateOfBirth; + + @NotNull(message = "Gender cannot be null.") + private String gender; + + private List sports = new ArrayList<>(); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserRatingDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserRatingDTO.java new file mode 100644 index 00000000..cf4ae8f7 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserRatingDTO.java @@ -0,0 +1,26 @@ +package com.sportsmatch.dtos; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@Getter +@Setter +public class UserRatingDTO { + + private String opponentName; + private String userTextRating; + private Integer userStarRating; + private LocalDateTime createdAt; + + public UserRatingDTO( + String opponentName, String userTextRating, Integer userStarRating, LocalDateTime createdAt) { + this.opponentName = opponentName; + this.userTextRating = userTextRating; + this.userStarRating = userStarRating; + this.createdAt = createdAt; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserRatingStatsDTO.java b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserRatingStatsDTO.java new file mode 100644 index 00000000..39b32049 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/dtos/UserRatingStatsDTO.java @@ -0,0 +1,27 @@ +package com.sportsmatch.dtos; + +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Setter +public class UserRatingStatsDTO { + private Map starRatingCounts; + private Double averageRating; + + public UserRatingStatsDTO() { + starRatingCounts = initializeStarRatingCounts(); + averageRating = 0.0; + } + + private Map initializeStarRatingCounts() { + Map result = new HashMap<>(); + for (int i = 1; i <= 5; i++) { + result.put(String.valueOf(i), 0); + } + return result; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/EventMapper.java b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/EventMapper.java new file mode 100644 index 00000000..9caefae6 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/EventMapper.java @@ -0,0 +1,82 @@ +package com.sportsmatch.mappers; + +import com.sportsmatch.dtos.EventDTO; +import com.sportsmatch.dtos.EventHistoryDTO; +import com.sportsmatch.dtos.HostEventDTO; +import com.sportsmatch.models.Event; +import com.sportsmatch.models.EventPlayer; +import com.sportsmatch.models.EventStatusOptions; +import org.modelmapper.ModelMapper; +import org.modelmapper.convention.MatchingStrategies; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class EventMapper { + + private final UserMapper userMapper; + private ModelMapper modelMapper; + + private PlaceMapper placeMapper; + + @Autowired + public EventMapper(ModelMapper modelMapper, UserMapper userMapper, PlaceMapper placeMapper) { + this.modelMapper = modelMapper; + this.userMapper = userMapper; + this.placeMapper = placeMapper; + this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); + } + + public EventDTO convertEventToEventDTO(Event event) { + EventDTO eventDTO = modelMapper.map(event, EventDTO.class); + List eventPlayers = event.getPlayers().stream().toList(); + if (eventPlayers.size() > 0) { + eventDTO.setPlayer1Id(eventPlayers.get(0).getPlayer().getId()); + eventDTO.setPlayer1Name(eventPlayers.get(0).getPlayer().getName()); + } + if (eventPlayers.size() > 1) { + eventDTO.setPlayer2Id(eventPlayers.get(1).getPlayer().getId()); + eventDTO.setPlayer2Name(eventPlayers.get(1).getPlayer().getName()); + } + eventDTO.setSport(event.getSport().getName()); + eventDTO.setPlaceDTO(placeMapper.toDTO(event.getPlace())); + return eventDTO; + } + + public Event convertHostEventDTOtoEvent(HostEventDTO hostEventDTO) { + modelMapper.typeMap(EventDTO.class, Event.class).addMappings(e -> e.skip(Event::setId)); + return modelMapper.map(hostEventDTO, Event.class); + } + + /** + * Convert an Event entity to and EventHistoryDTO. + * + * @param event to be converted + * @return an EventHistoryDTO based on the given Event + */ + public EventHistoryDTO toDTO(Event event, String loggedUsername, EventStatusOptions status) { + + // Get the logged-in EventPlayer + EventPlayer loggedPlayer = event.getPlayers().stream() + .filter(p -> p.getPlayer().getName().equals(loggedUsername)) + .findFirst() + .orElse(null); + + // Get the opponent EventPlayer + EventPlayer opponentPlayer = event.getPlayers().stream() + .filter(p -> !p.getPlayer().getName().equals(loggedUsername)) + .findFirst() + .orElse(null); + + + return EventHistoryDTO.builder() + .userScore((loggedPlayer != null) ? loggedPlayer.getMyScore() : null) + .opponentScore((opponentPlayer != null) ? opponentPlayer.getMyScore() : null) + .opponent((opponentPlayer != null) ? userMapper.toDTO(opponentPlayer.getPlayer()) : null) + .dateOfTheMatch(event.getDateEnd()) + .status(status) + .build(); + } +} \ No newline at end of file diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/PlaceMapper.java b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/PlaceMapper.java new file mode 100644 index 00000000..2eb97f64 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/PlaceMapper.java @@ -0,0 +1,40 @@ +package com.sportsmatch.mappers; + +import com.sportsmatch.dtos.PlaceDTO; +import com.sportsmatch.models.Place; +import org.springframework.stereotype.Component; + +@Component +public class PlaceMapper { + + /** + * Converts a Place entity into a PlaceDTO object. + * + * @param entity The entity containing the data of the Place. + * @return a PlaceDTO object with the given parameters from the Place entity. + */ + public PlaceDTO toDTO(Place entity) { + return PlaceDTO.builder() + .id(entity.getId()) + .name(entity.getName()) + .address(entity.getAddress()) + .latitude(entity.getLatitude()) + .longitude(entity.getLongitude()) + .build(); + } + + /** + * Converts a PlaceDTO object into a Place entity. + * + * @param dto The PlaceDTO object containing the data of the Place. + * @return a Place entity with the given parameters from the PlaceDTO object. + */ + public Place toEntity(PlaceDTO dto) { + return Place.builder() + .name(dto.getName()) + .address(dto.getAddress()) + .latitude(dto.getLatitude()) + .longitude(dto.getLongitude()) + .build(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/RatingMapper.java b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/RatingMapper.java new file mode 100644 index 00000000..d0c4e873 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/RatingMapper.java @@ -0,0 +1,22 @@ +package com.sportsmatch.mappers; + +import com.sportsmatch.dtos.RatingDTO; +import com.sportsmatch.models.Rating; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RatingMapper { + + public Rating toUserRatingEntity(RatingDTO ratingDTO) { + return Rating.builder() + .starRating(ratingDTO.getUserStarRating()) + .textRating(ratingDTO.getUserTextRating()) + .build(); + } + + public Rating toEventRatingEntity(RatingDTO ratingDTO) { + return Rating.builder().starRating(ratingDTO.getEventStarRating()).build(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/SportMapper.java b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/SportMapper.java new file mode 100644 index 00000000..1191d986 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/SportMapper.java @@ -0,0 +1,32 @@ +package com.sportsmatch.mappers; + +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.models.Sport; +import org.springframework.stereotype.Component; + +@Component +public class SportMapper { + + /** + * Converts a Sport entity to a SportDTO. + * + * @param entity The Sport entity to be converted. + * @return SportDTO containing information from the Sport entity. + */ + public SportDTO toDTO(Sport entity) { + return SportDTO.builder() + .name(entity.getName()) + .emoji(entity.getEmoji()) + .backgroundUImageURL(entity.getBackgroundImageURL()) + .id(entity.getId()) + .build(); + } + + public Sport toEntity(SportDTO sportDTO) { + return Sport.builder() + .name(sportDTO.getName()) + .emoji(sportDTO.getEmoji()) + .backgroundImageURL(sportDTO.getBackgroundUImageURL()) + .build(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/UserMapper.java b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/UserMapper.java new file mode 100644 index 00000000..e754295f --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/mappers/UserMapper.java @@ -0,0 +1,44 @@ +package com.sportsmatch.mappers; + +import com.sportsmatch.dtos.AuthRequestDTO; +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.dtos.UserDTO; +import com.sportsmatch.models.Role; +import com.sportsmatch.models.SportUser; +import com.sportsmatch.models.User; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class UserMapper { + + private final PasswordEncoder passwordEncoder; + private final SportMapper sportMapper; + + public User registerToUser(AuthRequestDTO authRequestDTO) { + return User.builder() + .email(authRequestDTO.getEmail()) + .password(passwordEncoder.encode(authRequestDTO.getPassword())) + .role(Role.USER) + .build(); + } + + public UserDTO toDTO(User user) { + List userSports = user.getSportUsers().stream() + .map(SportUser::getSport) + .map(sportMapper::toDTO) + .toList(); + return UserDTO.builder() + .id(user.getId()) + .name(user.getName()) + .sports(userSports) + .elo(user.getRank()) + .win(user.getWin()) + .loss(user.getLoss()) + .build(); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Event.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Event.java new file mode 100644 index 00000000..a36e6577 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Event.java @@ -0,0 +1,67 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "events") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Event { + + @Id @GeneratedValue private Long id; + + @Column(name = "date_start") + private LocalDateTime dateStart; + + @Column(name = "date_end") + private LocalDateTime dateEnd; + + @Column(name = "min_elo") + private Integer minElo; + + @Column(name = "max_elo") + private Integer maxElo; + + private String title; + + @Column(name = "is_rank_updated") + private Boolean isRanksUpdated = false; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "event") + private Set players = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "event") + private Set ratings = new HashSet<>(); + + @ManyToOne private Sport sport; + + @ManyToOne + @JoinColumn(name = "place_id") + private Place place; + + public Event( + LocalDateTime dateStart, + LocalDateTime dateEnd, + String location, + Integer minElo, + Integer maxElo, + String title, + Sport sport, + Place place) { + this.dateStart = dateStart; + this.dateEnd = dateEnd; + this.minElo = minElo; + this.maxElo = maxElo; + this.title = title; + this.sport = sport; + this.place = place; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/EventPlayer.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/EventPlayer.java new file mode 100644 index 00000000..ce814345 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/EventPlayer.java @@ -0,0 +1,39 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "event_player") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class EventPlayer { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "my_score") + private Integer myScore; + + @Column(name = "opponent_score") + private Integer opponentScore; + + @ManyToOne + private User player; + + @ManyToOne + private Event event; + + public EventPlayer(Integer myScore, Integer opponentScore, User player, Event event) { + this.myScore = myScore; + this.opponentScore = opponentScore; + this.player = player; + this.event = event; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/EventStatusOptions.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/EventStatusOptions.java new file mode 100644 index 00000000..a6adb80f --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/EventStatusOptions.java @@ -0,0 +1,9 @@ +package com.sportsmatch.models; + +public enum EventStatusOptions { + MATCH, + MISMATCH, + WAITING_FOR_RATING, + + INVALID_PLAYER +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Gender.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Gender.java new file mode 100644 index 00000000..fae2a09f --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Gender.java @@ -0,0 +1,15 @@ +package com.sportsmatch.models; + +import lombok.Getter; + +@Getter +public enum Gender { + MALE("Male"), + FEMALE("Female"), + OTHER("Other"); + private final String displayValue; + + Gender(String displayValue) { + this.displayValue = displayValue; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Place.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Place.java new file mode 100644 index 00000000..f77bedba --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Place.java @@ -0,0 +1,43 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +import java.util.HashSet; +import java.util.Set; + +@Entity +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "places") +public class Place { + + @Id + @GeneratedValue + public Long id; + + @NotBlank + public String name; + + @NotBlank + public String address; + + public double latitude; + + public double longitude; + + @OneToMany(mappedBy = "place") + private Set events = new HashSet<>(); + + + public Place(String name, String address, Double latitude, Double longitude) { + this.name = name; + this.address = address; + this.latitude = latitude; + this.longitude = longitude; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Rating.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Rating.java new file mode 100644 index 00000000..d4bcb301 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Rating.java @@ -0,0 +1,38 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Rating { + + @Id + @GeneratedValue + private Long id; + + @Column(name = "text_rating") + private String textRating; + + @Column(name = "star_rating") + private Integer starRating; + + @Column(name = "created_at") + @CreationTimestamp + private LocalDateTime createdAt; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "userRating") + private Set userRatings = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "eventRating") + private Set eventRatings = new HashSet<>(); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Role.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Role.java new file mode 100644 index 00000000..3ab52124 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Role.java @@ -0,0 +1,6 @@ +package com.sportsmatch.models; + +public enum Role { + USER, + ADMIN +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Sport.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Sport.java new file mode 100644 index 00000000..63fdc426 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Sport.java @@ -0,0 +1,44 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.*; + +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "sports") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Sport { + @Id + @GeneratedValue + private Long id; + + private String name; + + private String emoji; + + @Column(name = "background_image_url") + private String backgroundImageURL; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "sport") + private Set sportUsers = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "sport") + private Set events = new HashSet<>(); + + public Sport(String name) { + this.name = name; + } + + public Sport(String name, String emoji, String backgroundImageURL) { + this.name = name; + this.emoji = emoji; + this.backgroundImageURL = backgroundImageURL; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/SportUser.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/SportUser.java new file mode 100644 index 00000000..58176e62 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/SportUser.java @@ -0,0 +1,33 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "sports_users") +public class SportUser { + @EmbeddedId private SportUserKey id; + + @ManyToOne + @MapsId("userId") + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @MapsId("sportId") + @JoinColumn(name = "sport_id") + private Sport sport; + + public SportUser(User user, Sport sport) { + this.user = user; + this.sport = sport; + this.id = new SportUserKey(user.getId(), sport.getId()); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/SportUserKey.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/SportUserKey.java new file mode 100644 index 00000000..ecb79788 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/SportUserKey.java @@ -0,0 +1,44 @@ +package com.sportsmatch.models; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Objects; + +@Getter +@Setter +@NoArgsConstructor +@Embeddable +public class SportUserKey implements Serializable { + + @Column(name = "user_id") + private Long userId; + + @Column(name = "sport_id") + private Long sportId; + + public SportUserKey(Long userId, Long sportId) { + this.userId = userId; + this.sportId = sportId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SportUserKey that)) { + return false; + } + return Objects.equals(userId, that.userId) && Objects.equals(sportId, that.sportId); + } + + @Override + public int hashCode() { + return Objects.hash(userId, sportId); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/Token.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Token.java new file mode 100644 index 00000000..cad8332c --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/Token.java @@ -0,0 +1,25 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.*; + +@Setter +@Getter +@Builder +@Entity +@NoArgsConstructor +@AllArgsConstructor +public class Token { + + @Id + @GeneratedValue + private Long id; + + private String token; + private Boolean isValid; + + @Enumerated(EnumType.STRING) + private TokenType tokenType; + + @ManyToOne private User user; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/TokenType.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/TokenType.java new file mode 100644 index 00000000..f173e56d --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/TokenType.java @@ -0,0 +1,5 @@ +package com.sportsmatch.models; + +public enum TokenType { + BEARER +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/User.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/User.java new file mode 100644 index 00000000..3f76ca8a --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/User.java @@ -0,0 +1,116 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "users") +public class User implements UserDetails { + + @Id + @GeneratedValue + private Long id; + + @Column(unique = true) + private String email; + + private String password; + + private String name; + + private Gender gender; + + private Integer rank = 1000; + + private Integer win = 0; + + private Integer loss = 0; + + private Integer totalPlayed = 0; + + @Enumerated(EnumType.STRING) + private Role role; + + @Column(name = "date_of_birth") + @DateTimeFormat(pattern = "dd-MM-yyyy") + private LocalDate dateOfBirth; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") + private Set sportUsers = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "player") + private Set eventsPlayed = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") + private Set tokens = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "player") + private Set ratings = new HashSet<>(); + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "opponent") + private Set rated = new HashSet<>(); + + public User(String email, String password, String name, Gender gender, LocalDate dateOfBirth) { + this.email = email; + this.password = password; + this.name = name; + this.gender = gender; + this.dateOfBirth = dateOfBirth; + this.role = Role.USER; + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority(this.role.name())); + } + + @Override + public String getPassword() { + return this.password; + } + + /** + * Returns the user's email as the username. + * + * @return The email address. + */ + @Override + public String getUsername() { + return this.email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/models/UserEventRating.java b/backend/sportsmatch/src/main/java/com/sportsmatch/models/UserEventRating.java new file mode 100644 index 00000000..4b199da5 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/models/UserEventRating.java @@ -0,0 +1,23 @@ +package com.sportsmatch.models; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserEventRating { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne private Rating userRating; + @ManyToOne private Rating eventRating; + @ManyToOne private User player; + @ManyToOne private User opponent; + @ManyToOne private Event event; +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventPlayerRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventPlayerRepository.java new file mode 100644 index 00000000..b0617dc1 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventPlayerRepository.java @@ -0,0 +1,27 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.Event; +import com.sportsmatch.models.EventPlayer; +import com.sportsmatch.models.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface EventPlayerRepository extends JpaRepository { + + Optional findEventPlayerByPlayerAndEventId(User player, Long eventId); + + List findEventPlayersByEvent(Event event); + + Optional findEventPlayerByEventAndPlayer(Event event, User player); + + @Query( + "SELECT ep.event FROM EventPlayer ep JOIN Event e ON e.id = ep.event.id WHERE ep.player.id = :id AND e.dateEnd < :now") + List findPastEventsByPlayer(@Param("id") Long id, @Param("now") LocalDateTime now); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java new file mode 100644 index 00000000..435394a2 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/EventRepository.java @@ -0,0 +1,77 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.Event; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface EventRepository extends JpaRepository { + + Optional findEventById(Long id); + + List findAllBySportIdIn(List sportId); + + /** + * Retrieves events filtered by user and finished status. + * + * @param id the logged user's id to filter events by + * @param now the current time to filter events by + * @param pageable pagination information (page, size) + * @return a list of events filtered by user and finished status + */ + // =?1 =?2") + @Query("SELECT ep.event FROM EventPlayer ep WHERE ep.player.id = :id AND ep.event.dateEnd < :now") + List findEventsByUser( + @Param("id") Long id, @Param("now") LocalDateTime now, Pageable pageable); + + @Query( + "SELECT ep.event FROM EventPlayer ep WHERE ep.player.id = :id AND ep.event.dateStart > :now ORDER BY ep.event.dateEnd ASC") + List findUpcomingEventsByUser(@Param("id") Long id, @Param("now") LocalDateTime now); + + /** + * Finds events near the user location, optionally filtered by sport names. + * + *

This method uses a native SQL query with a Haversine distance calculation to find events + * ordered by their distance from the user's location. + * + * @param userLongitude user's longitude coordinate + * @param userLatitude user's latitude coordinate + * @param sportNames optional, list of sport names to filter events by. If null, all events are + * returned. Sport names are not case-sensitive. + * @param placeName optional, substring to search name of the place by. If null all places are + * returned. It is not case-sensitive. + * @param now date and time to search events by. Only events starting after this date and time are + * returned. + * @param pageable contains the page and size + * @return list of events filtered by sport names if given, and order by distance from the user's + * given location + */ + @Query( + nativeQuery = true, + value = + "SELECT e.id, e.date_start, e.date_end, e.min_elo, e.max_elo, e.title, e.is_rank_updated, e.sport_id, e.place_id " + + "FROM events e " + + "JOIN sports s ON e.sport_id = s.id " + + "JOIN places p ON e.place_id = p.id " + + "WHERE (LOWER(p.name) LIKE %:placeName%) AND (:sportNames IS NULL OR LOWER(s.name) IN(:sportNames)) AND (e.date_start > :now)" + + "ORDER BY ( " + + " 6371 * acos( " // Haversine distance calculation + + " cos(radians(p.latitude)) * cos(radians(:latitude)) * " + + " cos(radians(:longitude) - radians(p.longitude)) + " + + " sin(radians(p.latitude)) * sin(radians(:latitude))) " + + " ) ASC;") + List findNearbyEvents( + @Param("longitude") final double userLongitude, + @Param("latitude") final double userLatitude, + @Param("sportNames") final List sportNames, + @Param("placeName") final String placeName, + @Param("now") final LocalDateTime now, + Pageable pageable); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/PlaceRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/PlaceRepository.java new file mode 100644 index 00000000..cd4e4b11 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/PlaceRepository.java @@ -0,0 +1,19 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.Place; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface PlaceRepository extends JpaRepository { + + /** + * Search places by the given name. The search is not case-sensitive. + * + * @param name of the place to filter by. Set null to ignore this filter. + * @return a list of Place entity that match the specified criteria. + */ + @Query("SELECT p FROM Place p WHERE (:name IS NULL OR LOWER(p.name) LIKE LOWER(CONCAT('%', :name, '%'))) OR (:name IS NULL)") + List searchPlaces(String name); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/RatingRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/RatingRepository.java new file mode 100644 index 00000000..4df4e356 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/RatingRepository.java @@ -0,0 +1,9 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.Rating; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RatingRepository extends JpaRepository { +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/SportRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/SportRepository.java new file mode 100644 index 00000000..4dcfae48 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/SportRepository.java @@ -0,0 +1,13 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.Sport; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface SportRepository extends JpaRepository { + + Optional findSportByName(String name); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/SportUserRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/SportUserRepository.java new file mode 100644 index 00000000..88ec8619 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/SportUserRepository.java @@ -0,0 +1,9 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.SportUser; +import com.sportsmatch.models.SportUserKey; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SportUserRepository extends JpaRepository {} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/TokenRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/TokenRepository.java new file mode 100644 index 00000000..e43317a2 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/TokenRepository.java @@ -0,0 +1,11 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.Token; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TokenRepository extends JpaRepository { + + boolean existsByToken(String token); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/UserEventRatingRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/UserEventRatingRepository.java new file mode 100644 index 00000000..3010b286 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/UserEventRatingRepository.java @@ -0,0 +1,34 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.dtos.UserRatingDTO; +import com.sportsmatch.models.Event; +import com.sportsmatch.models.User; +import com.sportsmatch.models.UserEventRating; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface UserEventRatingRepository extends JpaRepository { + + @Query( + "SELECT r.starRating, COUNT(r.starRating) FROM UserEventRating uer JOIN Rating r ON uer.userRating.id = r.id " + + "WHERE uer.opponent.id = :id GROUP BY r.starRating") + List findRatingsCount(@Param("id") Long id); + + @Query( + "SELECT AVG(r.starRating) FROM UserEventRating uer JOIN Rating r ON uer.userRating.id = r.id WHERE uer.opponent.id = :id") + Optional findAverageRating(@Param("id") Long id); + + @Query( + "SELECT new com.sportsmatch.dtos.UserRatingDTO(uer.player.name, r.textRating, r.starRating, r.createdAt) FROM UserEventRating uer JOIN Rating r " + + "ON uer.userRating.id = r.id WHERE uer.opponent.id = :id ORDER BY r.createdAt DESC") + List findAllByOpponent(@Param("id") Long id, Pageable pageable); + + boolean existsByPlayerAndEvent(User player, Event event); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/UserRepository.java b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/UserRepository.java new file mode 100644 index 00000000..b3dbe57f --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/repositories/UserRepository.java @@ -0,0 +1,21 @@ +package com.sportsmatch.repositories; + +import com.sportsmatch.models.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + + @Transactional + Optional findUserById(Long id); + + Optional findByEmail(String email); + + Optional findUserByName(String name); + + boolean existsByEmail(String email); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java new file mode 100644 index 00000000..54f3083f --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/EventService.java @@ -0,0 +1,228 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.EventDTO; +import com.sportsmatch.dtos.EventHistoryDTO; +import com.sportsmatch.dtos.HostEventDTO; +import com.sportsmatch.dtos.RequestEventDTO; +import com.sportsmatch.mappers.EventMapper; +import com.sportsmatch.models.Event; +import com.sportsmatch.models.EventPlayer; +import com.sportsmatch.models.EventStatusOptions; +import com.sportsmatch.models.Place; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.EventPlayerRepository; +import com.sportsmatch.repositories.EventRepository; +import com.sportsmatch.repositories.PlaceRepository; +import com.sportsmatch.repositories.SportRepository; +import com.sportsmatch.repositories.UserRepository; +import lombok.AllArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class EventService { + private final UserService userService; + private final EventRepository eventRepository; + private final EventMapper eventMapper; + private final UserRepository userRepository; + private final SportRepository sportRepository; + private final EventPlayerRepository eventPlayerRepository; + private final PlaceRepository placeRepository; + + public Event getEventById(Long id) { + return eventRepository + .findEventById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + } + + public EventDTO getEventDTObyEventId(Long id) { + Event event = + eventRepository + .findEventById(id) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + EventDTO eventDTO = eventMapper.convertEventToEventDTO(event); + return eventDTO; + } + + public List getAllEvents() { + List eventList = eventRepository.findAll(); + List eventDTOList = new ArrayList<>(); + for (Event event : eventList) { + eventDTOList.add(getEventDTObyEventId(event.getId())); + } + return eventDTOList; + } + + public List getEventsBySports(List sportsIds) { + List eventDTOList = new ArrayList<>(); + List eventListBySport = eventRepository.findAllBySportIdIn(sportsIds); + for (Event event : eventListBySport) { + eventDTOList.add(getEventDTObyEventId(event.getId())); + } + return eventDTOList; + } + + public EventPlayer addPlayerToEvent(Long playerId, Long eventId) { + EventPlayer eventPlayer = new EventPlayer(); + eventPlayer.setPlayer( + userRepository + .findUserById(playerId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST))); + eventPlayer.setEvent( + eventRepository + .findEventById(eventId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST))); + return eventPlayerRepository.save(eventPlayer); + } + + public Event createEvent(HostEventDTO hostEventDTO) { + User user = userService.getUserFromContext(); + Event newEvent = eventMapper.convertHostEventDTOtoEvent(hostEventDTO); + Place place = placeRepository.findById(hostEventDTO.getLocationId()).orElseThrow(); + newEvent.setPlace(place); + newEvent.setSport( + sportRepository + .findSportByName(hostEventDTO.getSport()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST))); + newEvent = eventRepository.save(newEvent); + EventPlayer userPlayer = new EventPlayer(null, null, user, newEvent); + eventPlayerRepository.save(userPlayer); + return newEvent; + } + + public void deleteEventFromDatabase(Event eventById) { + eventRepository.deleteById(eventById.getId()); + } + + /** + * Retrieves the event history of the logged-in user. + * + * @param pageable contains the pagination information (page, size) + * @return a list of EventHistoryDTOs representing the logged-in user's event history$ ../gradlew + * clean checkStyleMain -info + */ + public List getEventsHistory(final Pageable pageable) { + String loggedUserName = userService.getUserFromContext().getName(); + Long loggedUserId = userService.getUserFromContext().getId(); + + return eventRepository.findEventsByUser(loggedUserId, LocalDateTime.now(), pageable).stream() + .map(event -> eventMapper.toDTO(event, loggedUserName, checkScoreMatch(event.getPlayers()))) + .collect(Collectors.toList()); + } + + /** + * Returns the checked status of the match (check the score is matching or missing). + * + * @param players who entered the event (2 playerEvent) + * @return the status of the match There is 4 option: - Invalid Player -> if one of the player + * don't present. - Waiting for ratings -> if one of the players doesn't response with the + * score information. - Match -> when both player submitted their result and it is match. - + * Mismatch -> when both players have submitted their result, and it isn't a match. + */ + public EventStatusOptions checkScoreMatch(Set players) { + + User loggedUser = userService.getUserFromContext(); + + EventPlayer loggedPlayer = + players.stream() + .filter(p -> p.getPlayer().getName().equals(loggedUser.getName())) + .findFirst() + .orElse(null); + + EventPlayer otherPlayer = + players.stream() + .filter(p -> !Objects.equals(p.getPlayer().getName(), loggedUser.getName())) + .findFirst() + .orElse(null); + + if (loggedPlayer == null || otherPlayer == null) { + return EventStatusOptions.INVALID_PLAYER; + } else if (loggedPlayer.getMyScore() == null + || loggedPlayer.getOpponentScore() == null + || otherPlayer.getMyScore() == null + || otherPlayer.getOpponentScore() == null) { + return EventStatusOptions.WAITING_FOR_RATING; + } + + int loggedPlayerOwnScore = loggedPlayer.getMyScore(); + int loggedPlayerOpponentScore = loggedPlayer.getOpponentScore(); + int otherPlayerOwnScore = otherPlayer.getMyScore(); + int otherPlayerLoggedScore = otherPlayer.getOpponentScore(); + + if (loggedPlayerOwnScore == otherPlayerLoggedScore + && loggedPlayerOpponentScore == otherPlayerOwnScore) { + return EventStatusOptions.MATCH; + } else { + return EventStatusOptions.MISMATCH; + } + } + + public void joinEvent(Long id) throws Exception { + Event event = getEventById(id); + User loggedUser = userService.getUserFromContext(); + Set eventPlayerSet = event.getPlayers(); + if (eventPlayerSet.size() <= 1 + && eventPlayerRepository.findEventPlayerByEventAndPlayer(event, loggedUser).isEmpty()) { + EventPlayer eventPlayer = new EventPlayer(); + eventPlayer.setPlayer(loggedUser); + eventPlayer.setEvent(event); + eventPlayerRepository.save(eventPlayer); + } else if (eventPlayerRepository + .findEventPlayerByEventAndPlayer(event, loggedUser) + .isPresent()) { + throw new Exception("User " + loggedUser.getName() + " has already joined the event."); + } else { + throw new Exception("Event has already two players."); + } + } + + /** + * Finds events near the provided location and filters them based on optional sport names filters, + * returning a page of event data transfer objects (DTOs). + * + * @param requestEventDTO containing the request parameters for filtering events. + * @param pageable containing page and size + * @return a list of EventDTO representing Events entity and filtered by sport names is given, and + * coordinate. + */ + public List getNearbyEvents(RequestEventDTO requestEventDTO, final Pageable pageable) { + + // Convert the given sportNames to lowercase because of the native custom query + List sportNamesWithLowerCase = + requestEventDTO.getSportsName().stream().map(String::toLowerCase).toList(); + + List events = + eventRepository.findNearbyEvents( + requestEventDTO.getLongitude(), + requestEventDTO.getLatitude(), + sportNamesWithLowerCase, + requestEventDTO.getPlaceName() == null + ? "" + : requestEventDTO.getPlaceName().toLowerCase(), + LocalDateTime.now(), + pageable); + + return events.stream().map(eventMapper::convertEventToEventDTO).collect(Collectors.toList()); + } + + /** + * Returns the upcoming matches of the logged-in user. + * + * @return a list of logged-in user's upcoming EventDTOs ordered by date ascending + */ + public List getUsersUpcomingEvents() { + User loggedUser = userService.getUserFromContext(); + return eventRepository + .findUpcomingEventsByUser(loggedUser.getId(), LocalDateTime.now()) + .stream() + .map(eventMapper::convertEventToEventDTO) + .collect(Collectors.toList()); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/PlaceService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/PlaceService.java new file mode 100644 index 00000000..0f42a42e --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/PlaceService.java @@ -0,0 +1,44 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.PlaceDTO; +import com.sportsmatch.mappers.PlaceMapper; +import com.sportsmatch.repositories.PlaceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PlaceService { + + private final PlaceMapper placeMapper; + private final PlaceRepository placeRepository; + + /** + * Adds a new Place to the database based on PlaceDTO. + * + * @param placeDTO object containing the data about the new Place. + * @return ResponseEntity with HTTP status(201) and message if it was successfully added. + */ + public ResponseEntity addNewPlace(PlaceDTO placeDTO) { + placeRepository.save(placeMapper.toEntity(placeDTO)); + return ResponseEntity.status(HttpStatus.CREATED).body("Place successfully added"); + } + + /** + * Searches for places based on name match and returns a list of corresponding PlaceDTO objects. + * + * @param name of the place to filter by. + * @return a list of PlaceDTO objects that match the specified criteria. + */ + public List searchPlaces(String name) { + return placeRepository.searchPlaces(name) + .stream() + .map(placeMapper::toDTO) + .collect(Collectors.toList()); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/RankService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/RankService.java new file mode 100644 index 00000000..126b4a22 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/RankService.java @@ -0,0 +1,113 @@ +package com.sportsmatch.services; + +import com.sportsmatch.models.Event; +import com.sportsmatch.models.EventPlayer; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.EventRepository; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RankService { + + // @Value("${app.sportsmingle.num-game-threshold}") + private final List NUM_GAME_THRESHOLD = new ArrayList<>(Arrays.asList(5, 15, 25)); + + // @Value("${app.sportsmingle.k-factors}") + private final List K_FACTORS = new ArrayList<>(Arrays.asList(25.0, 15.0, 10.0)); + + // @Value("${app.sportsmingle.k-factor-default}") + private final double DEFAULT_K_FACTOR = 5.0; + + private final EventRepository eventRepository; + + public void updatePlayersRanks(Event event) { + if (!isEventValid(event) || event.getIsRanksUpdated() || event.getPlayers().size() < 2) { + return; + } + + List players = new ArrayList<>(event.getPlayers()); + EventPlayer firstPlayer = players.get(0); + EventPlayer secondPlayer = players.get(1); + + firstPlayer.getPlayer().setRank(newRating(firstPlayer, secondPlayer)); + secondPlayer.getPlayer().setRank(newRating(secondPlayer, firstPlayer)); + event.setIsRanksUpdated(true); + + eventRepository.save(event); + } + + private double calculateTransformedRating(User user) { + return Math.pow(10, ((double) user.getRank() / 400)); + } + + private double expectedScore(User firstPlayer, User secondPlayer) { + double firstTransformedRating = calculateTransformedRating(firstPlayer); + double secondTransformedRating = calculateTransformedRating(secondPlayer); + return firstTransformedRating / (firstTransformedRating + secondTransformedRating); + } + + private int newRating(EventPlayer firstPlayer, EventPlayer secondPlayer) { + double updatedRating = + firstPlayer.getPlayer().getRank() + + adjustKFactor(firstPlayer.getPlayer()) + * ((getScore(firstPlayer)) + - expectedScore(firstPlayer.getPlayer(), secondPlayer.getPlayer())); + return (int) updatedRating; + } + + private double getScore(EventPlayer eventPlayer) { + double myScore = eventPlayer.getMyScore(); + double opponentScore = eventPlayer.getOpponentScore(); + User player = eventPlayer.getPlayer(); + + player.setTotalPlayed(player.getTotalPlayed() + 1); + + if (myScore > opponentScore) { + player.setWin(player.getWin() + 1); + return 1; + } else if (myScore == opponentScore) { + return 0.5; + } else { + player.setLoss(player.getLoss() + 1); + return 0; + } + } + + private double adjustKFactor(User user) { + for (int i = 0; i < NUM_GAME_THRESHOLD.size(); i++) { + if (user.getTotalPlayed() <= NUM_GAME_THRESHOLD.get(i)) { + return K_FACTORS.get(i); + } + } + return DEFAULT_K_FACTOR; + } + + private boolean isEventValid(Event event) { + EventPlayer firstPlayer = null; + + for (EventPlayer e : event.getPlayers()) { + + if (e.getMyScore() == null || e.getOpponentScore() == null) { + return false; + } + + int myScore = e.getMyScore(); + int opponentScore = e.getOpponentScore(); + + if (firstPlayer == null) { + firstPlayer = e; + } else { + if (myScore != firstPlayer.getOpponentScore() + || opponentScore != firstPlayer.getMyScore()) { + return false; + } + } + } + return true; + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/RatingService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/RatingService.java new file mode 100644 index 00000000..28bd95bc --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/RatingService.java @@ -0,0 +1,113 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.EventDTO; +import com.sportsmatch.dtos.RatingDTO; +import com.sportsmatch.dtos.UserRatingDTO; +import com.sportsmatch.dtos.UserRatingStatsDTO; +import com.sportsmatch.mappers.EventMapper; +import com.sportsmatch.mappers.RatingMapper; +import com.sportsmatch.models.*; +import com.sportsmatch.repositories.*; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.data.domain.Pageable; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class RatingService { + + private final RatingRepository ratingRepository; + private final UserEventRatingRepository userEventRatingRepository; + private final EventPlayerRepository eventPlayerRepository; + private final RatingMapper ratingMapper; + private final UserService userService; + private final EventMapper eventMapper; + + public void addRating(RatingDTO ratingDTO) { + User player = userService.getUserFromContext(); + EventPlayer eventPlayer = getEventPlayer(player, ratingDTO.getEventId()); + User opponent = findOpponent(eventPlayer, player); + Rating userRating = ratingMapper.toUserRatingEntity(ratingDTO); + Rating eventRating = ratingMapper.toEventRatingEntity(ratingDTO); + + eventPlayer.setMyScore(ratingDTO.getMyScore()); + eventPlayer.setOpponentScore(ratingDTO.getOpponentScore()); + + UserEventRating userEventRating = + UserEventRating.builder() + .userRating(userRating) + .eventRating(eventRating) + .player(player) + .opponent(opponent) + .event(eventPlayer.getEvent()) + .build(); + ratingRepository.save(userRating); + ratingRepository.save(eventRating); + userEventRatingRepository.save(userEventRating); + } + + private EventPlayer getEventPlayer(User player, Long eventId) { + Optional eventPlayerOptional = + eventPlayerRepository.findEventPlayerByPlayerAndEventId(player, eventId); + return eventPlayerOptional.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + private User findOpponent(EventPlayer eventPlayer, User player) { + List eventPlayers = + eventPlayerRepository.findEventPlayersByEvent(eventPlayer.getEvent()); + return eventPlayers.stream() + .map(EventPlayer::getPlayer) + .filter(p -> !p.getId().equals(player.getId())) + .findFirst() + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + public List findUnratedEvents() { + User player = userService.getUserFromContext(); + + return eventPlayerRepository + // fetching user's past events + .findPastEventsByPlayer(player.getId(), LocalDateTime.now()) + .stream() + // checking if there are two players in the event + .filter(e -> e.getPlayers().size() > 1) + // checking if user has already rated + .filter(e -> !userEventRatingRepository.existsByPlayerAndEvent(player, e)) + .map(eventMapper::convertEventToEventDTO) + .toList(); + } + + public UserRatingStatsDTO getUserRatingStats(Long id) { + UserRatingStatsDTO stats = new UserRatingStatsDTO(); + List counts = userEventRatingRepository.findRatingsCount(id); + Map ratingCounts = stats.getStarRatingCounts(); + for (Object[] row : counts) { + String starRating = String.valueOf(row[0]); + Long count = (Long) row[1]; + ratingCounts.put(starRating, count.intValue()); + } + + Optional userRatingAverage = userEventRatingRepository.findAverageRating(id); + if (userRatingAverage.isEmpty()) { + stats.setAverageRating(0.0); + return stats; + } + + BigDecimal average = BigDecimal.valueOf(userRatingAverage.get()); + stats.setAverageRating(average.setScale(1, RoundingMode.HALF_UP).doubleValue()); + return stats; + } + + public List getAllUserRatings(Long id, Pageable pageable) { + return userEventRatingRepository.findAllByOpponent(id, pageable); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/SportService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/SportService.java new file mode 100644 index 00000000..009305cf --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/SportService.java @@ -0,0 +1,11 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.SportDTO; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface SportService { + List getAllSports(final Pageable pageable); + +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/SportServiceImp.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/SportServiceImp.java new file mode 100644 index 00000000..122f8b08 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/SportServiceImp.java @@ -0,0 +1,31 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.mappers.SportMapper; +import com.sportsmatch.repositories.SportRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class SportServiceImp implements SportService { + + private final SportRepository sportRepository; + private final SportMapper sportMapper; + + /** + * {@summary

This method returns a paginated list of SportsDTO.

} + * + * @param pageable contains the page and size values for pagination. + * @return paginated list of SportDTO. + */ + public List getAllSports(final Pageable pageable) { + return sportRepository.findAll(pageable).stream() + .map(sportMapper::toDTO) + .collect(Collectors.toList()); + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/UserService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/UserService.java new file mode 100644 index 00000000..5070642d --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/UserService.java @@ -0,0 +1,18 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.UserDTO; +import com.sportsmatch.dtos.UserInfoDTO; +import com.sportsmatch.models.User; + +public interface UserService { + + User getUserFromContext(); + + UserDTO getUserDTOFromContext(); + + void updateUserInfo(UserInfoDTO userInfoDTO); + + UserDTO getUserById(Long id); + + UserDTO getMyRank(); +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/UserServiceImp.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/UserServiceImp.java new file mode 100644 index 00000000..86cface0 --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/UserServiceImp.java @@ -0,0 +1,117 @@ +package com.sportsmatch.services; + +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.dtos.UserDTO; +import com.sportsmatch.dtos.UserInfoDTO; +import com.sportsmatch.mappers.SportMapper; +import com.sportsmatch.mappers.UserMapper; +import com.sportsmatch.models.*; +import com.sportsmatch.repositories.SportRepository; +import com.sportsmatch.repositories.UserRepository; +import jakarta.transaction.Transactional; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; + +@Service +@AllArgsConstructor +public class UserServiceImp implements UserService { + + private final UserRepository userRepository; + private final SportMapper sportMapper; + private final SportRepository sportRepository; + private final UserMapper userMapper; + private final RankService rankService; + + /** + * This method retrieves the authenticated user from the SecurityContextHolder. It checks if the + * user is authenticated and returns the corresponding User entity. + * + * @return the authenticated user + * @throws ResponseStatusException if the user is not authenticated + */ + @Override + public User getUserFromContext() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null + && authentication.isAuthenticated() + && authentication.getPrincipal() instanceof UserDetails userDetails) { + return userRepository.findByEmail(userDetails.getUsername()).orElseThrow(); + } else { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not authenticated"); + } + } + + public UserDTO getUserDTOFromContext() { + return userMapper.toDTO(getUserFromContext()); + } + + public UserDTO getMyRank() { + User user = getUserFromContext(); + return getUserById(user.getId()); + } + + public UserDTO getUserById(Long id) { + Optional user = userRepository.findUserById(id); + + if (user.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND); + } + + List events = new ArrayList<>(user.get().getEventsPlayed()); + for (EventPlayer e : events) { + rankService.updatePlayersRanks(e.getEvent()); + } + + return userMapper.toDTO(user.get()); + } + + @Transactional + public void updateUserInfo(UserInfoDTO userInfoDTO) { + User user = getUserFromContext(); + user.setName(userInfoDTO.getUserName()); + parseUserDateOfBirth(userInfoDTO, user); + user.setGender(Gender.valueOf(userInfoDTO.getGender().toUpperCase())); + linkUserWithSport(userInfoDTO, user); + userRepository.save(user); + } + + private void parseUserDateOfBirth(UserInfoDTO userInfoDTO, User user) { + try { + user.setDateOfBirth( + LocalDate.parse(userInfoDTO.getDateOfBirth(), DateTimeFormatter.ofPattern("dd-MM-yyyy"))); + } catch (DateTimeParseException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + } + + private void linkUserWithSport(UserInfoDTO userInfoDTO, User user) { + for (SportDTO sportDTO : userInfoDTO.getSports()) { + Optional optionalSport = sportRepository.findSportByName(sportDTO.getName()); + + if (optionalSport.isPresent()) { + Sport sport = optionalSport.get(); + + boolean isAssociated = + user.getSportUsers().stream() + .anyMatch(sportUser -> Objects.equals(sportUser.getSport().getId(), sport.getId())); + + if (!isAssociated) { + SportUser sportUser = new SportUser(user, sport); + user.getSportUsers().add(sportUser); + } + } else { + throw new ResponseStatusException( + HttpStatus.NOT_FOUND, "Sport not found: " + sportDTO.getName()); + } + } + } +} diff --git a/backend/sportsmatch/src/main/java/com/sportsmatch/services/ValidationService.java b/backend/sportsmatch/src/main/java/com/sportsmatch/services/ValidationService.java new file mode 100644 index 00000000..9914dc9e --- /dev/null +++ b/backend/sportsmatch/src/main/java/com/sportsmatch/services/ValidationService.java @@ -0,0 +1,17 @@ +package com.sportsmatch.services; + +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.stereotype.Service; +import org.springframework.validation.BindingResult; + +import java.util.List; + +@Service +public class ValidationService { + + public List getAllErrors(BindingResult bindingResult) { + return bindingResult.getAllErrors().stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .toList(); + } +} diff --git a/backend/sportsmatch/src/main/resources/application-prod.properties b/backend/sportsmatch/src/main/resources/application-prod.properties new file mode 100644 index 00000000..eb146548 --- /dev/null +++ b/backend/sportsmatch/src/main/resources/application-prod.properties @@ -0,0 +1,22 @@ +spring.datasource.url=jdbc:postgresql://quirky-bugbear-7185.7tc.aws-eu-central-1.cockroachlabs.cloud:26257/sportsmingle?sslmode=verify-full +spring.datasource.username=matt +spring.datasource.password={ask for it} +spring.jpa.hibernate.ddl-auto=update +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.use_sql_comments=true +spring.jpa.properties.hibernate.generate_statistics=true +spring.h2.console.enabled=true +spring.mvc.hiddenmethod.filter.enabled=true + +spring.jpa.defer-datasource-initialization=true +spring.sql.init.mode=always + +app.sportsmingle.initialization.database-init=true +app.sportsmingle.jwt.secret={ask for it} +app.sportsmingle.frontend.url=sportsmingle.app + +app.sportsmingle.num-game-threshold=5,15,25 +app.sportsmingle.k-factors=25.0,15.0,10.0 +app.sportsmingle.k-factor-default=5.0 diff --git a/backend/sportsmatch/src/main/resources/application.properties b/backend/sportsmatch/src/main/resources/application.properties new file mode 100644 index 00000000..79aaaadd --- /dev/null +++ b/backend/sportsmatch/src/main/resources/application.properties @@ -0,0 +1,24 @@ +spring.datasource.url=jdbc:postgresql://localhost:26257/defaultdb +spring.datasource.username=root +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=create-drop +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.use_sql_comments=true +spring.jpa.properties.hibernate.generate_statistics=true +spring.h2.console.enabled=true +spring.mvc.hiddenmethod.filter.enabled=true + +spring.jpa.defer-datasource-initialization=true +spring.sql.init.mode=always + +app.sportsmingle.initialization.database-init=true +app.sportsmingle.jwt.secret=56e1a2c8b7a56f5c480cf0045186dbc514d9172e4fbbc81dd82f541384274c3c +app.sportsmingle.frontend.url=http://localhost:5173 + +app.sportsmingle.num-game-threshold=5,15,25 +app.sportsmingle.k-factors=25.0,15.0,10.0 +app.sportsmingle.k-factor-default=5.0 + +logging.level.org.springframework.security=DEBUG \ No newline at end of file diff --git a/backend/sportsmatch/src/main/resources/data.sql b/backend/sportsmatch/src/main/resources/data.sql new file mode 100644 index 00000000..c2e4f402 --- /dev/null +++ b/backend/sportsmatch/src/main/resources/data.sql @@ -0,0 +1,58 @@ +/* + +This is initial file for stored procedure + + +-- Cleanup +drop function if exists get_places_by_distance_and_sports_names; +drop type if exists event_rt; + +-- Create record type It need to be similar like the entity we want to get back (sequence is important!) +CREATE TYPE event_rt AS ( + id BIGINT, + date_start TIMESTAMP, + date_end TIMESTAMP, + min_elo INT, + max_elo INT, + title VARCHAR(255), + sport_id BIGINT, + place_id BIGINT + ); + +-- Create function, that returns the places by distance +CREATE FUNCTION get_places_by_distance_and_sports_names(p_longitude FLOAT, + p_latitude FLOAT, + sport_names VARCHAR []) + RETURNS SETOF event_rt AS +' +BEGIN +RETURN QUERY ( + SELECT + e.id, + e.date_start, + e.date_end, + e.min_elo, + e.max_elo, + e.title, + e.sport_id, + e.place_id +FROM + events e +JOIN + sports s ON e.sport_id = s.id +JOIN + places p ON e.place_id = p.id +WHERE + (:sport_name IS NULL OR LOWER(s.name) = LOWER(:sport_name)) +ORDER BY ( + 6371 * acos( -- Haversine distance calculation + cos(radians(p.latitude)) * cos(radians(:latitude)) * + cos(radians(:longitude) - radians(p.longitude)) + + sin(radians(p.latitude)) * sin(radians(:latitude)) + ) + ) ASC; +); +END; +' language plpgsql; + + */ \ No newline at end of file diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/BaseTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/BaseTest.java new file mode 100644 index 00000000..a3511dda --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/BaseTest.java @@ -0,0 +1,7 @@ +package com.sportsmatch; + +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +public class BaseTest { +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/SportsmatchApplicationTests.java b/backend/sportsmatch/src/test/java/com/sportsmatch/SportsmatchApplicationTests.java new file mode 100644 index 00000000..01776803 --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/SportsmatchApplicationTests.java @@ -0,0 +1,11 @@ +package com.sportsmatch; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SportsmatchApplicationTests extends BaseTest { + + @Test + void contextLoads() {} +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/controllers/PlaceControllerTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/controllers/PlaceControllerTest.java new file mode 100644 index 00000000..1e3dc94a --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/controllers/PlaceControllerTest.java @@ -0,0 +1,145 @@ +package com.sportsmatch.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sportsmatch.BaseTest; +import com.sportsmatch.auth.JwtService; +import com.sportsmatch.dtos.PlaceDTO; +import com.sportsmatch.models.Gender; +import com.sportsmatch.models.Role; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.UserRepository; +import com.sportsmatch.services.PlaceService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +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.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.util.Arrays; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +@SpringBootTest +class PlaceControllerTest extends BaseTest { + + @Autowired private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + + @Autowired private JwtService jwtService; + + @Autowired private UserRepository userRepository; + + @MockBean private PlaceService placeService; + + PlaceDTO createPlaceDTO1() { + return PlaceDTO.builder() + .name("Test Place Name1") + .address("Test Address1") + .latitude(123.456) + .longitude(789.012) + .build(); + } + + PlaceDTO createPlaceDTO2() { + return PlaceDTO.builder() + .name("Test Place Name2") + .address("Test Address2") + .latitude(123.456) + .longitude(789.012) + .build(); + } + + @Test + void addNewPlaceShouldReturn403NotAuthenticatedUser() throws Exception { + // Create a PlaceDTO object for testing + PlaceDTO placeDTO = createPlaceDTO1(); + + // Mock the behavior of placeService.addNewPlace() to return success response + when(placeService.addNewPlace(any(PlaceDTO.class))) + .thenReturn(ResponseEntity.ok("Place added successfully")); + + // Perform a POST request to add a new place without authentication + mockMvc + .perform( + MockMvcRequestBuilders.post("/api/v1/places/add") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(placeDTO))) + // Verify that the response status is 401 Forbidden + .andExpect(MockMvcResultMatchers.status().isUnauthorized()); + } + + @Test + @WithMockUser(username = "testuser") + void addNewPlaceShouldReturn200AndSuccessfulMessageAuthenticatedUser() throws Exception { + // Create a PlaceDTO object for testing + PlaceDTO placeDTO = createPlaceDTO1(); + + // Mock the behavior of placeService.addNewPlace() to return success response + when(placeService.addNewPlace(any(PlaceDTO.class))) + .thenReturn(ResponseEntity.ok("Place successfully added")); + + // Create User + User user = + User.builder() + .email("testuser@mail.com") + .password("password") + .name("testuser") + .gender(Gender.MALE) + .role(Role.USER) + .build(); + userRepository.save(user); + String token = jwtService.generateToken(user); + + // Perform a POST request to add a new place with authentication + mockMvc + .perform( + MockMvcRequestBuilders.post("/api/v1/places/add") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(placeDTO))) + // Verify that the response status is 200 OK + .andExpect(MockMvcResultMatchers.status().isOk()) + // Verify that the response content contains the expected success message + .andExpect(MockMvcResultMatchers.content().string("Place successfully added")); + } + + @Test + void searchPlaces() throws Exception { + + PlaceDTO placeDTO1 = createPlaceDTO1(); + PlaceDTO placeDTO2 = createPlaceDTO2(); + List expectedPlaces = Arrays.asList(placeDTO1, placeDTO2); + + // Mock the behavior of the placeService to return the expected list of places + when(placeService.searchPlaces(any(String.class))).thenReturn(expectedPlaces); + + // Perform a GET request to search for places with a name parameter "test" + mockMvc + .perform( + MockMvcRequestBuilders.get("/api/v1/places/search") + .param("name", "test") + .contentType(MediaType.APPLICATION_JSON)) + + // Verify the response is an array + .andExpect(MockMvcResultMatchers.jsonPath("$").isArray()) + // Verify the name of the first place in the response matches the name of the first expected + // place + .andExpect(MockMvcResultMatchers.jsonPath("$[0].name").value("Test Place Name1")) + // Verify the name of the second place in the response matches the name of the second + // expected place + .andExpect(MockMvcResultMatchers.jsonPath("$[1].name").value("Test Place Name2")); + } +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/services/EventServiceTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/services/EventServiceTest.java new file mode 100644 index 00000000..d65fabb9 --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/services/EventServiceTest.java @@ -0,0 +1,213 @@ +package com.sportsmatch.services; + +import com.sportsmatch.BaseTest; +import com.sportsmatch.models.Event; +import com.sportsmatch.models.EventPlayer; +import com.sportsmatch.models.EventStatusOptions; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.EventPlayerRepository; +import com.sportsmatch.repositories.EventRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class EventServiceTest extends BaseTest { + @Mock private EventRepository eventRepository; + @Mock private EventPlayerRepository eventPlayerRepository; + @Mock private UserService userService; + @InjectMocks private EventService eventService; + private User loggedUser; + private User otherUser; + + @BeforeEach + void setUp() { + loggedUser = new User(); + loggedUser = createUser("loggedUser"); + otherUser = new User(); + otherUser = createUser("otherUser"); + } + + // Create an user + private User createUser(String name) { + User user = new User(); + user.setName(name); + return user; + } + + // Create an event player + private EventPlayer createEventPlayer(User player, Integer myScore, Integer opponentScore) { + EventPlayer eventPlayer = new EventPlayer(); + eventPlayer.setPlayer(player); + eventPlayer.setMyScore(myScore); + eventPlayer.setOpponentScore(opponentScore); + return eventPlayer; + } + + @Test + void checkScoreMatchExpectStatus_MATCH() { + // ARRANGE: + + // Event players + EventPlayer loggedEventPlayer = createEventPlayer(loggedUser, 2, 2); + EventPlayer otherEventPlayer = createEventPlayer(otherUser, 2, 2); + + // Set of the eventPlayers + Set eventPlayers = new HashSet<>(); + eventPlayers.add(loggedEventPlayer); + eventPlayers.add(otherEventPlayer); + + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Act: + EventStatusOptions result = eventService.checkScoreMatch(eventPlayers); + + // Assert: + assertEquals(EventStatusOptions.MATCH, result); + } + + @Test + void checkScoreMatchExpectStatus_MISMATCH() { + // ARRANGE: + + // Event players + EventPlayer loggedEventPlayer = createEventPlayer(loggedUser, 3, 2); + EventPlayer otherEventPlayer = createEventPlayer(otherUser, 2, 5); + + // Set of the eventPlayers + Set eventPlayers = new HashSet<>(); + eventPlayers.add(loggedEventPlayer); + eventPlayers.add(otherEventPlayer); + + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Act: + EventStatusOptions result = eventService.checkScoreMatch(eventPlayers); + + // Assert: + assertEquals(EventStatusOptions.MISMATCH, result); + } + + @Test + void checkScoreMatchExpectStatus_WAITING_FOR_RATING() { + // ARRANGE: + + // Event players + EventPlayer loggedEventPlayer = createEventPlayer(loggedUser, 3, 2); + EventPlayer otherEventPlayer = createEventPlayer(otherUser, null, null); + + // Set of the eventPlayers + Set eventPlayers = new HashSet<>(); + eventPlayers.add(loggedEventPlayer); + eventPlayers.add(otherEventPlayer); + + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Act: + EventStatusOptions result = eventService.checkScoreMatch(eventPlayers); + + // Assert: + assertEquals(EventStatusOptions.WAITING_FOR_RATING, result); + } + + @Test + void checkScoreMatchExpectStatus_INVALID_PLAYER() { + // ARRANGE: + + // Event players + EventPlayer loggedEventPlayer = createEventPlayer(loggedUser, 3, 2); + + // Set of the eventPlayers + Set eventPlayers = new HashSet<>(); + eventPlayers.add(loggedEventPlayer); + + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Act: + EventStatusOptions result = eventService.checkScoreMatch(eventPlayers); + + // Assert: + assertEquals(EventStatusOptions.INVALID_PLAYER, result); + } + + @Test + void joinEventAddsUserToEvent() { + // Arrange: + // User: + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Event + Event event = new Event(); + event.setId(1L); + when(eventRepository.findEventById(1L)).thenReturn(Optional.of(event)); + + // EventPlayerRepository + when(eventPlayerRepository.findEventPlayerByEventAndPlayer(event, loggedUser)) + .thenReturn(Optional.empty()); + + // Act: + try { + eventService.joinEvent(1L); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // Assert + verify(eventPlayerRepository, times(1)).save(any(EventPlayer.class)); + } + + @Test + void joinEventThrowExceptionWhenEventIsFull() { + // Arrange: + // User: + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Event + Event event = new Event(); + event.setId(1L); + when(eventRepository.findEventById(1L)).thenReturn(Optional.of(event)); + + // Add players to Event + EventPlayer player1 = new EventPlayer(); + EventPlayer player2 = new EventPlayer(); + event.getPlayers().add(player1); + event.getPlayers().add(player2); + + // Assert: + assertThrows(Exception.class, () -> eventService.joinEvent(1L)); + } + + @Test + void joinEventThrowsExceptionWhenUserHasAlreadyJoined() { + // Arrange: + // User: + when(userService.getUserFromContext()).thenReturn(loggedUser); + + // Event + Event event = new Event(); + event.setId(1L); + when(eventRepository.findEventById(1L)).thenReturn(Optional.of(event)); + + // EventPlayerRepository + EventPlayer eventPlayer = new EventPlayer(); + eventPlayer.setEvent(event); + eventPlayer.setPlayer(loggedUser); + when(eventPlayerRepository.findEventPlayerByEventAndPlayer(event, loggedUser)) + .thenReturn(Optional.of(eventPlayer)); + + // Assert: + assertThrows(Exception.class, () -> eventService.joinEvent(1L)); + } +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/services/PlaceServiceTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/services/PlaceServiceTest.java new file mode 100644 index 00000000..64e5cd95 --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/services/PlaceServiceTest.java @@ -0,0 +1,112 @@ +package com.sportsmatch.services; + +import com.sportsmatch.BaseTest; +import com.sportsmatch.dtos.PlaceDTO; +import com.sportsmatch.mappers.PlaceMapper; +import com.sportsmatch.models.Place; +import com.sportsmatch.repositories.PlaceRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@SpringBootTest +public class PlaceServiceTest extends BaseTest { + @Mock + private PlaceRepository placeRepository; + @Mock + private PlaceMapper placeMapper; + @InjectMocks + private PlaceService placeService; + + Place createPlaceEntity() { + return Place.builder() + .name("Test Place") + .address("Test Address") + .latitude(123.456F) + .longitude((789.012F)) + .build(); + } + + PlaceDTO createPlaceDTO() { + return PlaceDTO.builder() + .name("Test Place") + .address("Test Address") + .latitude(123.456) + .longitude(789.012) + .build(); + } + + @Test + public void addNewPlaceShouldReturnSuccessMessage() { + Place placeEntity = createPlaceEntity(); + PlaceDTO placeDTO = createPlaceDTO(); + + // Mocking PlaceMapper behavior to return a predefined Place entity + when(placeMapper.toEntity(any())).thenReturn(placeEntity); + + // Invoking the method under test + ResponseEntity response = placeService.addNewPlace(placeDTO); + + // Verifying that placeRepository's save method is called once with the created placeEntity + verify(placeRepository, times(1)).save(placeEntity); + + // Asserting the response status code and body + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals("Place successfully added", response.getBody()); + } + + @Test + public void searchPlacesWithExistName() { + String name = "Test Place"; + Place placeEntity = createPlaceEntity(); + PlaceDTO placeDTO = createPlaceDTO(); + + // Mocking placeRepository behavior to return a list of places with the given name + List places = new ArrayList<>(); + places.add(placeEntity); + when(placeRepository.searchPlaces(name)).thenReturn(places); + + // Mocking PlaceMapper behavior to return a predefined PlaceDTO object + when(placeMapper.toDTO(placeEntity)).thenReturn(placeDTO); + + // Invoking the method under test + List foundPlaces = placeService.searchPlaces(name); + + // Verifying that placeRepository's searchPlaces method is called once with the given name + verify(placeRepository, times(1)).searchPlaces(name); + + // Asserting the size and attributes of the found places list + assertEquals(1, foundPlaces.size()); + assertEquals("Test Place", foundPlaces.get(0).getName()); + assertEquals("Test Address", foundPlaces.get(0).getAddress()); + assertEquals(123.456D, foundPlaces.get(0).getLatitude()); + assertEquals(789.012D, foundPlaces.get(0).getLongitude()); + } + + @Test + public void searchPlacesWithNonExistName() { + String name = "NonExistent Place"; + Place placeEntity = createPlaceEntity(); + PlaceDTO placeDTO = createPlaceDTO(); + + // Mocking placeRepository behavior to return an empty list of places + List places = new ArrayList<>(); + when(placeRepository.searchPlaces(name)).thenReturn(places); + + // Invoking the method under test + List foundPlaces = placeService.searchPlaces(name); + + // Verifying that placeRepository's searchPlaces method is called once with the given name + verify(placeRepository, times(1)).searchPlaces(name); + assertEquals(0, foundPlaces.size()); + } +} \ No newline at end of file diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/services/RankServiceTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/services/RankServiceTest.java new file mode 100644 index 00000000..90ec5169 --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/services/RankServiceTest.java @@ -0,0 +1,65 @@ +package com.sportsmatch.services; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.sportsmatch.BaseTest; +import com.sportsmatch.models.Event; +import com.sportsmatch.models.EventPlayer; +import com.sportsmatch.models.User; +import com.sportsmatch.repositories.EventRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.TestPropertySource; + +@ExtendWith(MockitoExtension.class) +@TestPropertySource(locations = "classpath:application-test.properties") +class RankServiceTest extends BaseTest { + + @Mock EventRepository eventRepository; + @InjectMocks RankService rankService; + + @Test + void updatePlayersRanks() { + + // User + User firstUser = new User(); + firstUser.setRank(1000); + firstUser.setId(1L); + + User secondUser = new User(); + secondUser.setRank(1000); + secondUser.setId(2L); + + // EventPlayer + EventPlayer firstPlayer = new EventPlayer(); + firstPlayer.setId(1L); + firstPlayer.setMyScore(5); + firstPlayer.setOpponentScore(2); + firstPlayer.setPlayer(firstUser); + + EventPlayer secondPlayer = new EventPlayer(); + secondPlayer.setId(2L); + secondPlayer.setMyScore(2); + secondPlayer.setOpponentScore(5); + secondPlayer.setPlayer(secondUser); + + // Event + Event event = new Event(); + event.setId(1L); + event.getPlayers().add(firstPlayer); + event.getPlayers().add(secondPlayer); + + rankService.updatePlayersRanks(event); + + assertEquals(1012, firstUser.getRank()); + assertEquals(987, secondUser.getRank()); + assertTrue(event.getIsRanksUpdated()); + verify(eventRepository, times(1)).save(event); + } +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/services/RatingServiceTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/services/RatingServiceTest.java new file mode 100644 index 00000000..d09ead0d --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/services/RatingServiceTest.java @@ -0,0 +1,188 @@ +package com.sportsmatch.services; + +import com.sportsmatch.BaseTest; +import com.sportsmatch.dtos.EventDTO; +import com.sportsmatch.dtos.RatingDTO; +import com.sportsmatch.dtos.UserRatingStatsDTO; +import com.sportsmatch.mappers.EventMapper; +import com.sportsmatch.mappers.RatingMapper; +import com.sportsmatch.models.*; +import com.sportsmatch.repositories.EventPlayerRepository; +import com.sportsmatch.repositories.RatingRepository; +import com.sportsmatch.repositories.UserEventRatingRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class RatingServiceTest extends BaseTest { + + @Mock private RatingRepository ratingRepository; + @Mock private UserEventRatingRepository userEventRatingRepository; + @Mock private EventPlayerRepository eventPlayerRepository; + @Mock private RatingMapper ratingMapper; + @Mock private UserService userService; + @InjectMocks private RatingService ratingService; + @Mock private EventMapper eventMapper; + + @Test + void addRating() { + RatingDTO ratingDTO = + RatingDTO.builder() + .userTextRating("it was a long game") + .userStarRating(2) + .eventStarRating(2) + .myScore(10) + .opponentScore(9) + .eventId(1L) + .build(); + + // Authentication and Player + User player = mock(User.class); + when(userService.getUserFromContext()).thenReturn(player); + + // EventPlayer + EventPlayer eventPlayer = mock(EventPlayer.class); + when(eventPlayerRepository.findEventPlayerByPlayerAndEventId(any(), any())) + .thenReturn(Optional.of(eventPlayer)); + + // Opponent + Event event = mock(Event.class); + when(eventPlayer.getEvent()).thenReturn(event); + when(player.getId()).thenReturn(1L); + + User opponent = mock(User.class); + EventPlayer opponentEvent = mock(EventPlayer.class); + when(opponentEvent.getPlayer()).thenReturn(opponent); + when(opponent.getId()).thenReturn(2L); + + List eventPlayers = new ArrayList<>(); + eventPlayers.add(opponentEvent); + eventPlayers.add(eventPlayer); + + when(eventPlayerRepository.findEventPlayersByEvent(event)).thenReturn(eventPlayers); + + // Mapping + Rating userRating = mock(Rating.class); + Rating eventRating = mock(Rating.class); + when(ratingMapper.toUserRatingEntity(ratingDTO)).thenReturn(userRating); + when(ratingMapper.toEventRatingEntity(ratingDTO)).thenReturn(eventRating); + + // Score + doNothing().when(eventPlayer).setMyScore(ratingDTO.getMyScore()); + doNothing().when(eventPlayer).setOpponentScore(ratingDTO.getOpponentScore()); + when(ratingRepository.save(any(Rating.class))).thenReturn(userRating).thenReturn(eventRating); + + // Rating + UserEventRating userEventRating = mock(UserEventRating.class); + when(userEventRatingRepository.save(any(UserEventRating.class))).thenReturn(userEventRating); + + ratingService.addRating(ratingDTO); + + verify(ratingRepository, times(2)).save(any(Rating.class)); + verify(userEventRatingRepository, times(1)).save(any(UserEventRating.class)); + verify(eventPlayer).setMyScore(ratingDTO.getMyScore()); + verify(eventPlayer).setOpponentScore(ratingDTO.getOpponentScore()); + } + + @Test + void testFindUnratedEventsWithUnratedEvent() { + // Arrange: + // Authentication and Player + User player = new User(); + player.setId(1L); + when(userService.getUserFromContext()).thenReturn(player); + + // EventPlayers + EventPlayer eventPlayer1 = mock(EventPlayer.class); + EventPlayer eventPlayer2 = mock(EventPlayer.class); + Set eventPlayers = new HashSet<>(); + eventPlayers.add(eventPlayer1); + eventPlayers.add(eventPlayer2); + + // Event + List pastEvents = new ArrayList<>(); + Event event = new Event(); + event.setPlayers(eventPlayers); + + // Find player's past events + pastEvents.add(event); + when(eventPlayerRepository.findPastEventsByPlayer(anyLong(), any(LocalDateTime.class))) + .thenReturn(pastEvents); + + // Check if event is rated + when(userEventRatingRepository.existsByPlayerAndEvent(any(User.class), any(Event.class))) + .thenReturn(false); + + // Mapping + EventDTO eventDTO = new EventDTO(); + when(eventMapper.convertEventToEventDTO(event)).thenReturn(eventDTO); + + // Act: + List unratedEvents = ratingService.findUnratedEvents(); + + // Assert: + assertEquals(1, unratedEvents.size()); + } + + @Test + void testFindUnratedEventsWithRatedEvent() { + // Arrange: + // Authentication and Player + User player = new User(); + player.setId(1L); + when(userService.getUserFromContext()).thenReturn(player); + + // Event + List pastEvents = new ArrayList<>(); + when(eventPlayerRepository.findPastEventsByPlayer(anyLong(), any(LocalDateTime.class))) + .thenReturn(pastEvents); + + // Act: + List unratedEvents = ratingService.findUnratedEvents(); + + // Assert: + assertEquals(0, unratedEvents.size()); + } + + @Test + void getUserRatingStatsReturnsCorrectAverageRating() { + // Arrange: + // Average rating + when(userEventRatingRepository.findAverageRating(anyLong())).thenReturn(Optional.of(2.6666)); + + // Act + UserRatingStatsDTO result = ratingService.getUserRatingStats(1L); + + // Assert + assertEquals(2.7, result.getAverageRating()); + } + + @Test + void getUserRatingStatsReturnsMapOfStarRatings() { + // Arrange: + // Rating counts + List counts = new ArrayList<>(); + counts.add(new Object[] {2, 2L}); + counts.add(new Object[] {1, 1L}); + when(userEventRatingRepository.findRatingsCount(anyLong())).thenReturn(counts); + + // Act + UserRatingStatsDTO result = ratingService.getUserRatingStats(1L); + + // Assert + assertEquals(1, result.getStarRatingCounts().get("1")); + assertEquals(2, result.getStarRatingCounts().get("2")); + assertEquals(0, result.getStarRatingCounts().get("3")); + assertEquals(0, result.getStarRatingCounts().get("4")); + assertEquals(0, result.getStarRatingCounts().get("5")); + } +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/services/SportServiceImpTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/services/SportServiceImpTest.java new file mode 100644 index 00000000..509d9dca --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/services/SportServiceImpTest.java @@ -0,0 +1,65 @@ +package com.sportsmatch.services; + +import com.sportsmatch.BaseTest; +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.mappers.SportMapper; +import com.sportsmatch.models.Sport; +import com.sportsmatch.repositories.SportRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SportServiceImpTest extends BaseTest { + + @Mock private SportRepository sportRepository; + + @Mock private SportMapper sportMapper; + + @InjectMocks private SportServiceImp sportService; + + @Test + void getAllSportsShouldReturnAllSportsWhenRequired() { + // Arrange + Pageable pageable = Mockito.mock(Pageable.class); + + String footballEmoji = "\uD83E\uDD45"; // Goal net emoji + String basketballEmoji = "\uD83C\uDFC0"; // basketball emoji orange ball + + Sport sport1 = new Sport("Football", footballEmoji, "urlFootball"); + Sport sport2 = new Sport("Basketball", basketballEmoji, "urlBasketball"); + + List sports = Arrays.asList(sport1, sport2); + Page sportsPage = new PageImpl<>(sports, pageable, sports.size()); + + SportDTO sportDTO1 = new SportDTO("Football", footballEmoji, "urlFootball", 1L); + SportDTO sportDTO2 = new SportDTO("Basketball", basketballEmoji, "urlBasketball", 2L); + + when(sportMapper.toDTO(sport1)).thenReturn(sportDTO1); + when(sportMapper.toDTO(sport2)).thenReturn(sportDTO2); + + List expectedSportDTOs = Arrays.asList(sportDTO1, sportDTO2); + + // Mocking repository + when(sportRepository.findAll(any(Pageable.class))).thenReturn(sportsPage); + + // Act + List result = sportService.getAllSports(pageable); + + // Assert + assertEquals(expectedSportDTOs, result); + } +} diff --git a/backend/sportsmatch/src/test/java/com/sportsmatch/services/UserServiceImpTest.java b/backend/sportsmatch/src/test/java/com/sportsmatch/services/UserServiceImpTest.java new file mode 100644 index 00000000..56b8b539 --- /dev/null +++ b/backend/sportsmatch/src/test/java/com/sportsmatch/services/UserServiceImpTest.java @@ -0,0 +1,163 @@ +package com.sportsmatch.services; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import com.sportsmatch.BaseTest; +import com.sportsmatch.dtos.SportDTO; +import com.sportsmatch.dtos.UserDTO; +import com.sportsmatch.dtos.UserInfoDTO; +import com.sportsmatch.mappers.SportMapper; +import com.sportsmatch.mappers.UserMapper; +import com.sportsmatch.models.*; +import com.sportsmatch.repositories.SportRepository; +import com.sportsmatch.repositories.UserRepository; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; + +@ExtendWith(MockitoExtension.class) +class UserServiceImpTest extends BaseTest { + + @Mock private UserRepository userRepository; + @Mock private SportRepository sportRepository; + @Mock private SportMapper sportMapper; + @Mock private SecurityContext securityContext; + @Mock private RankService rankService; + @Mock private PasswordEncoder passwordEncoder; + private UserServiceImp userServiceImp; + + @BeforeEach + void initialize() { + userServiceImp = + new UserServiceImp( + userRepository, + sportMapper, + sportRepository, + new UserMapper(passwordEncoder, sportMapper), + rankService); + } + + @Test + void updateUserInfo() { + List sports = new ArrayList<>(); + SportUser sportUser = mock(SportUser.class); + Set sportUsers = new HashSet<>(); + UserInfoDTO userInfoDTO = + UserInfoDTO.builder() + .userName("John Doe") + .dateOfBirth("01-01-2024") + .gender("male") + .sports(sports) + .build(); + + // getUserFromTheSecurityContextHolder behaviour + Authentication authentication = mock(Authentication.class); + UserDetails userDetails = mock(UserDetails.class); + User user = mock(User.class); + SecurityContextHolder.setContext(securityContext); + when(securityContext.getAuthentication()).thenReturn(authentication); + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getPrincipal()).thenReturn(userDetails); + when(userRepository.findByEmail(any())).thenReturn(Optional.of(user)); + + // name + doNothing().when(user).setName(userInfoDTO.getUserName()); + + // parse date of birth + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + LocalDate localDate = LocalDate.parse(userInfoDTO.getDateOfBirth(), formatter); + doNothing().when(user).setDateOfBirth(localDate); + + // gender + doNothing().when(user).setGender(Gender.valueOf(userInfoDTO.getGender().toUpperCase())); + + // mapper + SportDTO sportDTO = mock(SportDTO.class); + Sport sport = mock(Sport.class); + when(sportMapper.toEntity(sportDTO)).thenReturn(sport); + + // link user with sport + sportUsers.add(sportUser); + when(user.getSportUsers()).thenReturn(sportUsers); + user.getSportUsers().add(sportUser); + when(userRepository.save(any())).thenReturn(user); + + userServiceImp.updateUserInfo(userInfoDTO); + + verify(user).setName("John Doe"); + verify(user) + .setDateOfBirth(LocalDate.parse("01-01-2024", DateTimeFormatter.ofPattern("dd-MM-yyyy"))); + verify(user).setGender(Gender.MALE); + Sport returnedSport = sportMapper.toEntity(sportDTO); + assertEquals(sport, returnedSport); + verify(user, times(1)).getSportUsers(); + assertTrue(sportUsers.contains(sportUser)); + verify(userRepository).save(user); + } + + @Test + void getUserById() { + String username = "testUser"; + + // User + User user = new User(); + user.setId(1L); + user.setName(username); + user.setRank(1000); + when(userRepository.findUserById(user.getId())).thenReturn(Optional.of(user)); + + // Sport + Sport sport = new Sport(); + sport.setId(1L); + sport.setName("Tennis"); + sport.setEmoji(""); + sport.setBackgroundImageURL("./assets/sport-component-tennis.png"); + + // SportUser + SportUser sportUser = new SportUser(user, sport); + user.getSportUsers().add(sportUser); + + // SportDTO + SportDTO sportDTO = + SportDTO.builder() + .name(sport.getName()) + .emoji(sport.getEmoji()) + .backgroundUImageURL(sport.getBackgroundImageURL()) + .build(); + + List sports = new ArrayList<>(); + sports.add(sportDTO); + + // Event + Event event = new Event(); + event.setSport(sport); + + // EventPlayer (no need to mock) + EventPlayer eventPlayer = new EventPlayer(); + eventPlayer.setEvent(event); // Set the event directly + + user.getEventsPlayed().add(eventPlayer); + + // No need to mock eventPlayer.getEvent() + + UserDTO expectedUserDTO = + UserDTO.builder().name(user.getName()).elo(user.getRank()).sports(sports).build(); + + UserDTO actualUserDTO = userServiceImp.getUserById(user.getId()); + + assertEquals(expectedUserDTO.getName(), actualUserDTO.getName()); + assertEquals(expectedUserDTO.getElo(), actualUserDTO.getElo()); + assertEquals(expectedUserDTO.getSports().size(), actualUserDTO.getSports().size()); + } +} diff --git a/backend/sportsmatch/src/test/resources/application-test.properties b/backend/sportsmatch/src/test/resources/application-test.properties new file mode 100644 index 00000000..c44eedcf --- /dev/null +++ b/backend/sportsmatch/src/test/resources/application-test.properties @@ -0,0 +1,9 @@ +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +app.sportsmingle.num-game-threshold=5,15,25 +app.sportsmingle.k-factors=25.0,15.0,10.0 +app.sportsmingle.k-factor-default=5.0 \ No newline at end of file diff --git a/frontend/sportsmatch-app/.env b/frontend/sportsmatch-app/.env new file mode 100644 index 00000000..ed5ee609 --- /dev/null +++ b/frontend/sportsmatch-app/.env @@ -0,0 +1 @@ +VITE_BACKEND_URL=http://localhost:8080 \ No newline at end of file diff --git a/frontend/sportsmatch-app/.env.production b/frontend/sportsmatch-app/.env.production new file mode 100644 index 00000000..408a3f1b --- /dev/null +++ b/frontend/sportsmatch-app/.env.production @@ -0,0 +1 @@ +VITE_BACKEND_URL= \ No newline at end of file diff --git a/frontend/sportsmatch-app/.eslintrc.json b/frontend/sportsmatch-app/.eslintrc.json new file mode 100644 index 00000000..05881c96 --- /dev/null +++ b/frontend/sportsmatch-app/.eslintrc.json @@ -0,0 +1,56 @@ +{ + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "prettier", + "plugin:prettier/recommended", + "plugin:import/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint", + "react", + "react-hooks" + ], + "rules": { + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": [ "error" ], + "react/jsx-filename-extension": [ "warn", { "extensions": [ ".tsx" ] } ], + "import/extensions": [ "error", "ignorePackages", { "ts": "never", "tsx": "never" } ], + "no-shadow": "off", + "@typescript-eslint/no-shadow": [ "error" ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": "off", + "max-len": [ "warn", { "code": 100, "ignoreComments": true, "ignoreUrls": true } ], + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "import/prefer-default-export": "off", + "react/prop-types": "off", + "prettier/prettier": [ "error", { "endOfLine": "auto" } ], + "react/react-in-jsx-scope": "off", + "react/jsx-uses-react": "off", + "import/no-unresolved": "off" + + }, + "settings": { + "react": { + "version": "detect" + }, + "import/resolver": { + "typescript": {} + } + } +} diff --git a/frontend/sportsmatch-app/.gitignore b/frontend/sportsmatch-app/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/frontend/sportsmatch-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/sportsmatch-app/README.md b/frontend/sportsmatch-app/README.md new file mode 100644 index 00000000..0d6babed --- /dev/null +++ b/frontend/sportsmatch-app/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/frontend/sportsmatch-app/assets/Filter.png b/frontend/sportsmatch-app/assets/Filter.png new file mode 100644 index 00000000..1da2f43b Binary files /dev/null and b/frontend/sportsmatch-app/assets/Filter.png differ diff --git a/frontend/sportsmatch-app/assets/InProgress.png b/frontend/sportsmatch-app/assets/InProgress.png new file mode 100644 index 00000000..eb556b2e Binary files /dev/null and b/frontend/sportsmatch-app/assets/InProgress.png differ diff --git a/frontend/sportsmatch-app/assets/basketball_bg.jpg b/frontend/sportsmatch-app/assets/basketball_bg.jpg new file mode 100644 index 00000000..5153eeef Binary files /dev/null and b/frontend/sportsmatch-app/assets/basketball_bg.jpg differ diff --git a/frontend/sportsmatch-app/assets/favicon.png b/frontend/sportsmatch-app/assets/favicon.png new file mode 100644 index 00000000..2ad237d5 Binary files /dev/null and b/frontend/sportsmatch-app/assets/favicon.png differ diff --git a/frontend/sportsmatch-app/assets/female.png b/frontend/sportsmatch-app/assets/female.png new file mode 100644 index 00000000..346738e0 Binary files /dev/null and b/frontend/sportsmatch-app/assets/female.png differ diff --git a/frontend/sportsmatch-app/assets/img-event-card-badminton.png b/frontend/sportsmatch-app/assets/img-event-card-badminton.png new file mode 100644 index 00000000..748b1a48 Binary files /dev/null and b/frontend/sportsmatch-app/assets/img-event-card-badminton.png differ diff --git a/frontend/sportsmatch-app/assets/jeffrey-keenan-pUhxoSapPFA-unsplash.jpg b/frontend/sportsmatch-app/assets/jeffrey-keenan-pUhxoSapPFA-unsplash.jpg new file mode 100644 index 00000000..1b95b47e Binary files /dev/null and b/frontend/sportsmatch-app/assets/jeffrey-keenan-pUhxoSapPFA-unsplash.jpg differ diff --git a/frontend/sportsmatch-app/assets/logo.png b/frontend/sportsmatch-app/assets/logo.png new file mode 100644 index 00000000..8febf842 Binary files /dev/null and b/frontend/sportsmatch-app/assets/logo.png differ diff --git a/frontend/sportsmatch-app/assets/male.png b/frontend/sportsmatch-app/assets/male.png new file mode 100644 index 00000000..440d28b8 Binary files /dev/null and b/frontend/sportsmatch-app/assets/male.png differ diff --git a/frontend/sportsmatch-app/assets/man.svg b/frontend/sportsmatch-app/assets/man.svg new file mode 100644 index 00000000..a9b598f8 --- /dev/null +++ b/frontend/sportsmatch-app/assets/man.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/sportsmatch-app/assets/michael-dam-mEZ3PoFGs_k-unsplash.jpg b/frontend/sportsmatch-app/assets/michael-dam-mEZ3PoFGs_k-unsplash.jpg new file mode 100644 index 00000000..d3a6608f Binary files /dev/null and b/frontend/sportsmatch-app/assets/michael-dam-mEZ3PoFGs_k-unsplash.jpg differ diff --git a/frontend/sportsmatch-app/assets/sport-component-badminton.png b/frontend/sportsmatch-app/assets/sport-component-badminton.png new file mode 100644 index 00000000..626c540e Binary files /dev/null and b/frontend/sportsmatch-app/assets/sport-component-badminton.png differ diff --git a/frontend/sportsmatch-app/assets/sport-component-boxing.png b/frontend/sportsmatch-app/assets/sport-component-boxing.png new file mode 100644 index 00000000..bee10bbc Binary files /dev/null and b/frontend/sportsmatch-app/assets/sport-component-boxing.png differ diff --git a/frontend/sportsmatch-app/assets/sport-component-squash.png b/frontend/sportsmatch-app/assets/sport-component-squash.png new file mode 100644 index 00000000..1638dc09 Binary files /dev/null and b/frontend/sportsmatch-app/assets/sport-component-squash.png differ diff --git a/frontend/sportsmatch-app/assets/sport-component-table-tennis.png b/frontend/sportsmatch-app/assets/sport-component-table-tennis.png new file mode 100644 index 00000000..d25d9aae Binary files /dev/null and b/frontend/sportsmatch-app/assets/sport-component-table-tennis.png differ diff --git a/frontend/sportsmatch-app/assets/sport-component-tennis.png b/frontend/sportsmatch-app/assets/sport-component-tennis.png new file mode 100644 index 00000000..8ef7ad57 Binary files /dev/null and b/frontend/sportsmatch-app/assets/sport-component-tennis.png differ diff --git a/frontend/sportsmatch-app/assets/unknown-user-placeholder.png b/frontend/sportsmatch-app/assets/unknown-user-placeholder.png new file mode 100644 index 00000000..cd782ba6 Binary files /dev/null and b/frontend/sportsmatch-app/assets/unknown-user-placeholder.png differ diff --git a/frontend/sportsmatch-app/assets/woman.svg b/frontend/sportsmatch-app/assets/woman.svg new file mode 100644 index 00000000..aa3d1f93 --- /dev/null +++ b/frontend/sportsmatch-app/assets/woman.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/sportsmatch-app/index.html b/frontend/sportsmatch-app/index.html new file mode 100644 index 00000000..ec5a36d8 --- /dev/null +++ b/frontend/sportsmatch-app/index.html @@ -0,0 +1,13 @@ + + + + + + + SPORTS MINGLE + + +
+ + + diff --git a/frontend/sportsmatch-app/package-lock.json b/frontend/sportsmatch-app/package-lock.json new file mode 100644 index 00000000..bb4f14de --- /dev/null +++ b/frontend/sportsmatch-app/package-lock.json @@ -0,0 +1,7686 @@ +{ + "name": "sportsmatch-app", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sportsmatch-app", + "version": "0.0.0", + "dependencies": { + "bootstrap": "^5.3.2", + "react": "^18.2.0", + "react-bootstrap": "^2.10.0", + "react-datepicker": "^6.6.0", + "react-dom": "^18.2.0", + "react-icons": "^5.0.1", + "react-router-dom": "^6.21.3", + "react-social-login-buttons": "^3.9.1", + "reactjs-social-login": "^2.6.3" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.3.0", + "@testing-library/react": "^14.1.2", + "@types/react": "^18.2.43", + "@types/react-datepicker": "^6.2.0", + "@types/react-dom": "^18.2.17", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-functional": "^6.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "husky": "^9.0.6", + "jsdom": "^24.0.0", + "lint-staged": "^15.2.0", + "openapi-typescript-codegen": "^0.27.0", + "prettier": "^3.2.4", + "typescript": "^5.2.2", + "vite": "^5.0.8", + "vitest": "^1.2.2" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", + "dev": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-10.1.0.tgz", + "integrity": "sha512-3e+viyMuXdrcK8v5pvP+SDoAQ77FH6OyRmuK48SZKmdHJRFm87RsSs8qm6kP39a/pOPURByJw+OXzQIqcfmKtA==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.11", + "@types/lodash.clonedeep": "^4.5.7", + "js-yaml": "^4.1.0", + "lodash.clonedeep": "^4.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/code-frame/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/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/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/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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/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.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.11", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.11.tgz", + "integrity": "sha512-fo01Cu+jzLDVG/AYAV2OtV6flhXvxP5rDaR1Fk8WWhtsFqwk478Dr2HGtB8s0HqQCsFWVbdHYpPjMiQiR/A9VA==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.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": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "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/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "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/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "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/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.1.tgz", + "integrity": "sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", + "integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.15.tgz", + "integrity": "sha512-cZFXYTxbpzYcieq/mBwSyXgqnGMHoBVh3J7MU0CCoIB4NRZxV9/TuwTBAaLMqpNhC3zTPMCgkQ5Ey07L02Xmcw==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.6.tgz", + "integrity": "sha512-eC3puKuWE1SRYbojWHXnvCNHGgf3uzHCb6JOhnF4OXPibOIPEkR1sqDSkL643ydigxwh+ruCa1CmYHlzk7ikKA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@popperjs/core": "^2.11.6", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", + "@types/warning": "^3.0.0", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", + "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", + "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", + "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", + "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", + "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", + "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", + "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", + "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", + "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", + "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", + "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", + "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", + "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz", + "integrity": "sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/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/@testing-library/dom/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/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.3.0.tgz", + "integrity": "sha512-hJVIrkFizEQxoWsGBlycTcQhrpoCH4DhXfrnHFFXgkx3Xdm15zycsq5Ep+vpw4W8S0NJa8cxDHcuJib+1tEbhg==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.3.2", + "@babel/runtime": "^7.9.2", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@jest/globals": ">= 28", + "@types/bun": "latest", + "@types/jest": ">= 28", + "jest": ">= 28", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "@jest/globals": { + "optional": true + }, + "@types/bun": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "jest": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/react": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", + "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.202", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", + "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "dev": true + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/react": { + "version": "18.2.48", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", + "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-datepicker": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.2.0.tgz", + "integrity": "sha512-+JtO4Fm97WLkJTH8j8/v3Ldh7JCNRwjMYjRaKh4KHH0M3jJoXtwiD3JBCsdlg3tsFIw9eQSqyAPeVDN2H2oM9Q==", + "dev": true, + "dependencies": { + "@floating-ui/react": "^0.26.2", + "@types/react": "*", + "date-fns": "^3.3.1" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.1.tgz", + "integrity": "sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/type-utils": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.1.tgz", + "integrity": "sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz", + "integrity": "sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.1.tgz", + "integrity": "sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.19.1", + "@typescript-eslint/utils": "6.19.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.1.tgz", + "integrity": "sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz", + "integrity": "sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/visitor-keys": "6.19.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.1.tgz", + "integrity": "sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.19.1", + "@typescript-eslint/types": "6.19.1", + "@typescript-eslint/typescript-estree": "6.19.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz", + "integrity": "sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.19.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", + "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.2.2", + "@vitest/utils": "1.2.2", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", + "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.2.2", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", + "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/snapshot/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/@vitest/spy": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", + "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", + "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "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/ansi-escapes": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz", + "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==", + "dev": true, + "dependencies": { + "type-fest": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "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/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/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz", + "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "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/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/bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "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/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.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "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": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001581", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", + "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, + "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/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/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "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": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "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/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", + "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "dev": true, + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "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/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/deepmerge-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.648", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz", + "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", + "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", + "dev": true, + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.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-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": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "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.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "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.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "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.3", + "strip-ansi": "^6.0.1", + "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-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/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/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/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/eslint-plugin-functional": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-functional/-/eslint-plugin-functional-6.0.0.tgz", + "integrity": "sha512-jOUHUMA9cN2CIpgPj93fW1vTI3c95ZYUHMPJxEJL4KAtFkJDcT/9/YlfyrLOBxHkAcwBhJ29HSmeC/CUnN0k3g==", + "dev": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + } + ], + "dependencies": { + "@typescript-eslint/utils": "^6.2.0", + "deepmerge-ts": "^5.1.0", + "escape-string-regexp": "^4.0.0", + "is-immutable-type": "^2.0.1", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": ">=16.10.0" + }, + "peerDependencies": { + "eslint": "^8.0.0", + "typescript": ">=4.3.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/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-plugin-import/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/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.12", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.4", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/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-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/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-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.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/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "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/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.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.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/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "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-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "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.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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/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/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/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": 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/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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": "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/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "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": "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/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "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/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.6.tgz", + "integrity": "sha512-EEuw/rfTiMjOfuL7pGO/i9otg1u36TXxqjIA6D9qxVjd/UXoDOsLor/BSFf5hTK50shwzCU3aVVwdXDp/lp7RA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "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/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "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/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/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/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.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-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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-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.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-immutable-type": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-immutable-type/-/is-immutable-type-2.0.1.tgz", + "integrity": "sha512-SNO0yWLzSN+oYb8adM4AvsPYSCqElmjcXUNemryDLo0r5M54oMs/6R4cvKLc9QtIs/nRuc3ahlgJoMdGfcHLwQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/type-utils": "^6.0.0", + "ts-api-utils": "^1.0.1" + }, + "peerDependencies": { + "eslint": "*", + "typescript": ">=4.7.4" + } + }, + "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-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "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==", + "dev": true, + "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-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "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-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "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.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "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/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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.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": "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/jsdom": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", + "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "dev": true, + "dependencies": { + "cssstyle": "^4.0.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.7", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.16.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "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/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.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.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "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/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/lint-staged": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.0.tgz", + "integrity": "sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ==", + "dev": true, + "dependencies": { + "chalk": "5.3.0", + "commander": "11.1.0", + "debug": "4.3.4", + "execa": "8.0.1", + "lilconfig": "3.0.0", + "listr2": "8.0.0", + "micromatch": "4.0.5", + "pidtree": "0.6.0", + "string-argv": "0.3.2", + "yaml": "2.3.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.0.tgz", + "integrity": "sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/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.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "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-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "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/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "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/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-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": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "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/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "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/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "dev": true + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "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.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-typescript-codegen": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.27.0.tgz", + "integrity": "sha512-QyQEod/vuel3zfnTRC3GgmYsqLPSBzB2OL4ojMYjO9hJmfYW02T+7tbQWEnuqWdhh2KSOBf3L8h59vLStr6vwA==", + "dev": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^10.1.0", + "camelcase": "^6.3.0", + "commander": "^11.1.0", + "fs-extra": "^11.2.0", + "handlebars": "^4.7.8" + }, + "bin": { + "openapi": "bin/index.js" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/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/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/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "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-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/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, + "node_modules/prop-types-extra/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-bootstrap": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.0.tgz", + "integrity": "sha512-87gRP69VAfeU2yKgp8RI3HvzhPNrnYIV2QNranYXataz3ef+k7OhvKGGdxQLQfUsQ2RTmlY66tn4pdFrZ94hNg==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.6", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-datepicker": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.6.0.tgz", + "integrity": "sha512-ERC0/Q4pPC9bNIcGUpdCbHc+oCxhkU3WI3UOGHkyJ3A9fqALCYpEmLc5S5xvAd7DuCDdbsyW97oRPM6pWWwjww==", + "dependencies": { + "@floating-ui/react": "^0.26.2", + "clsx": "^2.1.0", + "date-fns": "^3.3.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-icons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", + "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz", + "integrity": "sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==", + "dependencies": { + "@remix-run/router": "1.14.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.3.tgz", + "integrity": "sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==", + "dependencies": { + "@remix-run/router": "1.14.2", + "react-router": "6.21.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-social-login-buttons": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/react-social-login-buttons/-/react-social-login-buttons-3.9.1.tgz", + "integrity": "sha512-KtucVWvdnIZ0icG99WJ3usQUJYmlKsOIBYGyngcuNSVyyYdZtif4KHY80qnCg+teDlgYr54ToQtg3x26ZqaS2w==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.x || ^18.x" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reactjs-social-login": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/reactjs-social-login/-/reactjs-social-login-2.6.3.tgz", + "integrity": "sha512-i/pcyJPtlVpHoPRr/j5/KjaP/GNxAInnnHDk0PD2Zo0B3cMdvr0ZiGC/GMjOKAAgKCujyniDf/OiZ7c6MrOLQg==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", + "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.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": "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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/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/restore-cursor/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/restore-cursor/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/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.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "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": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, + "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/safe-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", + "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "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/set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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-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/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/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-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, + "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/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/tinybench": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", + "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", + "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "dev": true, + "engines": { + "node": ">=14.0.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/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "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-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/vite": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", + "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", + "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.2.2", + "@vitest/runner": "1.2.2", + "@vitest/snapshot": "1.2.2", + "@vitest/spy": "1.2.2", + "@vitest/utils": "1.2.2", + "acorn-walk": "^8.3.2", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.2", + "vite": "^5.0.0", + "vite-node": "1.2.2", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "^1.0.0", + "@vitest/ui": "^1.0.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/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-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "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.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "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/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "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/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "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": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "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" + } + } + } +} diff --git a/frontend/sportsmatch-app/package.json b/frontend/sportsmatch-app/package.json new file mode 100644 index 00000000..b51eba3b --- /dev/null +++ b/frontend/sportsmatch-app/package.json @@ -0,0 +1,70 @@ +{ + "name": "sportsmatch-app", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext tsx --report-unused-disable-directives --max-warnings 0", + "lint:fix": "eslint --fix ./src", + "preview": "vite preview", + "test": "vitest", + "format": "prettier --check ./src", + "format:fix": "prettier --write ./src", + "generate": "openapi --input http://localhost:8080/v3/api-docs --output ./src/generated/api" + }, + "dependencies": { + "bootstrap": "^5.3.2", + "react": "^18.2.0", + "react-bootstrap": "^2.10.0", + "react-datepicker": "^6.6.0", + "react-dom": "^18.2.0", + "react-icons": "^5.0.1", + "react-router-dom": "^6.21.3", + "react-social-login-buttons": "^3.9.1", + "reactjs-social-login": "^2.6.3" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.3.0", + "@testing-library/react": "^14.1.2", + "@types/react": "^18.2.43", + "@types/react-datepicker": "^6.2.0", + "@types/react-dom": "^18.2.17", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.55.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-functional": "^6.0.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "husky": "^9.0.6", + "jsdom": "^24.0.0", + "lint-staged": "^15.2.0", + "openapi-typescript-codegen": "^0.27.0", + "prettier": "^3.2.4", + "typescript": "^5.2.2", + "vite": "^5.0.8", + "vitest": "^1.2.2" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "src/**/*.{js,jsx,ts,tsx}": [ + "eslint --fix", + "vitest" + ], + "./src/**": [ + "prettier --write ." + ] + } +} diff --git a/frontend/sportsmatch-app/src/.prettierignore b/frontend/sportsmatch-app/src/.prettierignore new file mode 100644 index 00000000..fdd8e2bb --- /dev/null +++ b/frontend/sportsmatch-app/src/.prettierignore @@ -0,0 +1,4 @@ +node_modules +# Ignore artifacts: +build +Coverage \ No newline at end of file diff --git a/frontend/sportsmatch-app/src/.prettierrc.json b/frontend/sportsmatch-app/src/.prettierrc.json new file mode 100644 index 00000000..6b8ddda0 --- /dev/null +++ b/frontend/sportsmatch-app/src/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "useTabs": false +} \ No newline at end of file diff --git a/frontend/sportsmatch-app/src/App.css b/frontend/sportsmatch-app/src/App.css new file mode 100644 index 00000000..dca74825 --- /dev/null +++ b/frontend/sportsmatch-app/src/App.css @@ -0,0 +1,51 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@700&display=swap'); + +:root { + --sm-primary-font: 'Inter', sans-serif; + --sm-secondary-font: 'Manrope', sans-serif; + + --sm-primary-bg: #F9F8F5; + --sm-orange: #e85f29; + --sm-dark: #4a494f; + --sm-white: #ffffff; + --sm-txt-dark: #393939; + --sm-secondary-orange: #fc4c02; + --sm-orange-overlay: rgba(183, 85, 45, 0.37); + + --sm-border-shadow: 0px 0px 5px rgba(0, 0, 0, 0.05); + --sm-border-radius: 2px; + + --sm-btn-size-regular: 3.125em; + --sm-btn-size-small: 1.875em; + + /* font-sizes */ + --sm-h1-heading: 3em; + --sm-text-content: 1em; + --sm-text-medium-content: 1.2em; +} + +svg { + color: var(--sm-white); +} + +body, +html, +#root { + height: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Inter', sans-serif; + background-color: var(--sm-primary-bg); +} + +.centered-container { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + min-height: 100%; + background-size: cover; + background-position: center; +} diff --git a/frontend/sportsmatch-app/src/App.tsx b/frontend/sportsmatch-app/src/App.tsx new file mode 100644 index 00000000..5b8d2a61 --- /dev/null +++ b/frontend/sportsmatch-app/src/App.tsx @@ -0,0 +1,57 @@ +import { BrowserRouter, Route, Routes } from 'react-router-dom' +import './App.css' +import Home from './pages/Home' +import Login from './pages/Login' +import Signup from './pages/Signup' +import Wrapper from './pages/AppWrapper' +import Index from './pages/Index' +import PrivateRoute from './components/PrivateRoute' +import NotFound from './pages/NotFound' +import { OpenAPI } from './generated/api' +import UserInfo from './pages/UserInfo' +import UserRating from './pages/UserRating' +import HostEvent from './pages/HostEvent' +import { useEffect } from 'react' + +function App() { + OpenAPI.BASE = import.meta.env.VITE_BACKEND_URL + OpenAPI.TOKEN = localStorage.getItem('token')! + + useEffect(() => { + document.title = 'SPORTS MINGLE' + }) + + return ( + + + {/* public routes */} + } />} /> + } />} /> + } acitvateCheckRatingModal={true} /> + } + /> + }> + + {/* private routes */} + }> + } />} /> + } />} /> + } />} + /> + } />} /> + } />} + /> + + + + ) +} + +export default App diff --git a/frontend/sportsmatch-app/src/components/AllSportsList.tsx b/frontend/sportsmatch-app/src/components/AllSportsList.tsx new file mode 100644 index 00000000..01d5eaba --- /dev/null +++ b/frontend/sportsmatch-app/src/components/AllSportsList.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react' +import { SportControllerService, SportDTO } from '../generated/api' +import '../App.css' +import '../styles/Sport.css' +import { SearchBar } from './SearchBar' + +interface AllSportsProps { + selectedButtonSports: string[] // contains selected buttons from the parent element + toggle: () => void + onSelect: (sports: string[]) => void // passes back selected buttons to the parent element +} + +export function AllSportsList(p: AllSportsProps) { + // fetching sports from the backend + const [allSports, setAllSports] = useState([]) + useEffect(() => { + const fetchSports = async () => { + setAllSports( + (await SportControllerService.getSports(0, 999)) as SportDTO[], + ) + } + fetchSports() + }, []) + + // handling the searchbar + const [searchQuery, setSearchQuery] = useState('') + const [selectedSports, setSelectedSports] = useState( + p.selectedButtonSports, + ) + // handling cliking on the sport button + const handleSportSelection = (buttonText: string) => { + setSelectedSports((prevState) => { + if (prevState.includes(buttonText)) { + return prevState.filter((button) => button !== buttonText) + } else { + return [...prevState, buttonText] + } + }) + } + + // handling passing the selected sports to the parent and closses the window + const handleFinishSelection = (sports: string[]) => { + p.onSelect(sports) + p.toggle() + } + + // rendering sports filtered by the query from the searchbar + const sportList: React.ReactElement[] = allSports + .filter((s) => s.name?.toLowerCase().includes(searchQuery.toLowerCase())) + .map((currentSport, index) => { + return ( +
+
+
+ +
+
+
+ ) + }) + + return ( + <> +
+ { + setSearchQuery(query) + }} + placeholder="Find your sport" + /> + {sportList} +
+
+ +
+
+
+ + ) +} diff --git a/frontend/sportsmatch-app/src/components/Avatar.tsx b/frontend/sportsmatch-app/src/components/Avatar.tsx new file mode 100644 index 00000000..485e0b96 --- /dev/null +++ b/frontend/sportsmatch-app/src/components/Avatar.tsx @@ -0,0 +1,11 @@ +import '../styles/Avatar.css' + +interface AvatarProps { + src: string +} + +const Avatar: React.FC = ({ src }) => ( +
+) + +export default Avatar diff --git a/frontend/sportsmatch-app/src/components/CheckRatingModal.tsx b/frontend/sportsmatch-app/src/components/CheckRatingModal.tsx new file mode 100644 index 00000000..e3bbc83f --- /dev/null +++ b/frontend/sportsmatch-app/src/components/CheckRatingModal.tsx @@ -0,0 +1,31 @@ +import { useEffect } from 'react' +import Modal from '../components/Modal' +import RateGameComponent from '../components/RateGameComponent' +import { RatingControllerService, ApiError, OpenAPI } from '../generated/api' +import useModal from '../hooks/UseModal' + +export default function CheckRatingModal() { + const { isOpen, toggle } = useModal() + useEffect(() => { + const init = async () => { + try { + OpenAPI.TOKEN = localStorage.getItem('token')! + const response = await RatingControllerService.checkRating() + if (response[0] && !isOpen) { + toggle() + } + } catch (error) { + console.error(error as ApiError) + } + } + init() + }) + + return ( + <> + + + + + ) +} diff --git a/frontend/sportsmatch-app/src/components/EventHistoryItem.tsx b/frontend/sportsmatch-app/src/components/EventHistoryItem.tsx new file mode 100644 index 00000000..6f520d61 --- /dev/null +++ b/frontend/sportsmatch-app/src/components/EventHistoryItem.tsx @@ -0,0 +1,95 @@ +import { useEffect, useState } from 'react' +import { EventHistoryDTO } from '../generated/api' +import '../styles/EventHistoryItem.css' +import Avatar from './Avatar' + +interface EventHistoryProps { + eventHistoryDTO: EventHistoryDTO +} + +function EventHistoryItem({ eventHistoryDTO }: EventHistoryProps) { + const [eventStatus, setEventStatus] = useState('') + + useEffect(() => { + const matchResult = + eventHistoryDTO.userScore! > eventHistoryDTO.opponentScore! + ? 'VICTORY' + : eventHistoryDTO.userScore! == eventHistoryDTO.opponentScore! + ? 'DRAW' + : 'DEFEAT' + if ( + eventHistoryDTO.status?.includes(EventHistoryDTO.status.MATCH) && + !eventHistoryDTO.status?.includes(EventHistoryDTO.status.MISMATCH) + ) { + setEventStatus(matchResult) + } else if ( + eventHistoryDTO.status?.includes( + EventHistoryDTO.status.WAITING_FOR_RATING, + ) + ) { + const result = matchResult + ' (UNCONFIRMED)' + setEventStatus(result) + } else if ( + eventHistoryDTO.status?.includes(EventHistoryDTO.status.MISMATCH) + ) { + setEventStatus('SCORE MISMATCH') + } + }, [ + eventHistoryDTO.opponentScore, + eventHistoryDTO.status, + eventHistoryDTO.userScore, + ]) + + return ( + <> +
+
+
+
{eventStatus}
+
+
+
+ +
+
You
+
+
+ + {eventHistoryDTO.status?.includes( + EventHistoryDTO.status.MISMATCH, + ) + ? '?' + : eventHistoryDTO.userScore} + +
+
+
+ : +
+
+
+ + {eventHistoryDTO.status?.includes( + EventHistoryDTO.status.MISMATCH, + ) + ? '?' + : eventHistoryDTO.opponentScore} + +
+
+
+ +
+
+ {eventHistoryDTO.opponent?.name} +
+
+
+
+
+
+ + ) +} + +export default EventHistoryItem diff --git a/frontend/sportsmatch-app/src/components/HostEventComponent.tsx b/frontend/sportsmatch-app/src/components/HostEventComponent.tsx new file mode 100644 index 00000000..213ea0e0 --- /dev/null +++ b/frontend/sportsmatch-app/src/components/HostEventComponent.tsx @@ -0,0 +1,300 @@ +import { FormEvent, useEffect, useState } from 'react' +import '../styles/HostEventComponent.css' +import { + PlaceControllerService, + SportControllerService, + SportDTO, + PlaceDTO, + EventsControllerService, + HostEventDTO, + EventDTO, + ApiError, +} from '../generated/api' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import { format } from 'date-fns' +import { useNavigate } from 'react-router-dom' +import LoadingSpinner from './LoadingSpinner' +import useModal from '../hooks/UseModal' +import SportEvent from './SportEvent' +import Modal from './Modal' +import JoinEventComponent from './JoinEventComponent' +import { SearchBar } from './SearchBar' + +function HostEventComponent() { + const [matchTitle, setMatchTitle] = useState('') + const [selectSport, setSelectedSport] = useState('') + const [sportsOptions, setSportsOptions] = useState([]) + const [selectRank, setSelectedRank] = useState('') + const rankOptions = Array.from({ length: 20 }, (_, index) => ({ + value: index * 500 + 1, + label: `${index * 500 + 1} - ${(index + 1) * 500}`, + })) + const [selectOppGender, setSelectedOppGender] = useState('') + const genderOptions = ['Male', 'Female'] + const [selectLocation, setSelectedLocation] = useState() + const [locationsOptions, setLocationOptions] = useState([]) + const [selectStartDateAndTime, setStartDateAndTime] = useState( + null, + ) + const [selectEndDateAndTime, setEndDateAndTime] = useState(null) + const navigate = useNavigate() + const [nearbyEvents, setNearbyEvents] = useState([]) + const [selectedEvent, setSelectedEvent] = useState() + const { isOpen, toggle } = useModal() + const [searchQuery, setSearchQuery] = useState('') // no implementation yet + + console.log(searchQuery) + + useEffect(() => { + SportControllerService.getSports().then((response) => { + setSportsOptions(response) + }) + PlaceControllerService.searchPlaces('').then((response) => + setLocationOptions(response), + ) + }, []) + + const handleHostEvent = async (e: FormEvent) => { + e.preventDefault() + + const [minElo, maxElo] = selectRank + .split(' - ') + .map((str) => parseInt(str.trim(), 10)) + + const formattedStartDate = selectStartDateAndTime + ? format(selectStartDateAndTime, "yyyy-MM-dd'T'HH:mm:ss") + : '' + const formattedEndDate = selectEndDateAndTime + ? format(selectEndDateAndTime, "yyyy-MM-dd'T'HH:mm:ss") + : '' + const event: HostEventDTO = { + dateStart: formattedStartDate, + dateEnd: formattedEndDate, + minElo: minElo, + maxElo: maxElo, + title: matchTitle, + sport: selectSport, + locationId: selectLocation!, + } + EventsControllerService.addEvent(event).then((response) => { + navigate('/app') + }) + } + + const handleSportSelection = ( + event: React.ChangeEvent, + ) => { + setSelectedSport(event.target.value) + } + const handleRankSelection = (event: React.ChangeEvent) => { + setSelectedRank(event.target.value) + } + const handleOppGenderSelection = ( + event: React.ChangeEvent, + ) => { + setSelectedOppGender(event.target.value) + } + const handleLocationSelection = ( + event: React.ChangeEvent, + ) => { + setSelectedLocation(parseInt(event.target.value)) + } + const handleStartDateSelection = (date: Date | null) => { + setStartDateAndTime(date) + } + const handleEndDateSelection = (date: Date | null) => { + setEndDateAndTime(date) + } + + // get nearby events + useEffect(() => { + const fetchData = async () => { + try { + const response = await EventsControllerService.getNearbyEvents( + undefined, + undefined, + undefined, + undefined, + undefined, + 999, + ) + if (!Array.isArray(response)) { + throw new Error('Failed to fetch event data') + } + const data: EventDTO[] = response as EventDTO[] + setNearbyEvents(data) + } catch (error) { + console.error(error as ApiError) + } + } + fetchData() + }, []) + + // handle join event pop up after cliking on the event + const handleEventSelection = (e: EventDTO) => { + if (isOpen) { + toggle() + } + setSelectedEvent(e) + toggle() + } + + return ( + <> +
+
+
+
+
+
+ + setMatchTitle(e.target.value)} + required + /> +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+
+ { + setSearchQuery(query) + }} + placeholder="search" + /> +
+
+ + + +
+
+
+ {nearbyEvents.length === 0 ? ( + + ) : ( + nearbyEvents.map((event, index) => ( +
handleEventSelection(event)} + > + +
+ )) + )} +
+
+
+
+
+ + ) +} + +export default HostEventComponent diff --git a/frontend/sportsmatch-app/src/components/JoinEventComponent.tsx b/frontend/sportsmatch-app/src/components/JoinEventComponent.tsx new file mode 100644 index 00000000..7af01b34 --- /dev/null +++ b/frontend/sportsmatch-app/src/components/JoinEventComponent.tsx @@ -0,0 +1,165 @@ +import { useEffect, useState } from 'react' +import { + OpenAPI, + EventsControllerService, + ApiError, + EventDTO, + ExSecuredEndpointService, + UserDTO, +} from '../generated/api' +import '../styles/JoinEvent.css' +import { Link } from 'react-router-dom' + +interface JoinEventProps { + event: EventDTO + toggle: () => void +} + +export default function JoinEventComponent(p: JoinEventProps) { + const handleJoinEvent = async () => { + try { + OpenAPI.TOKEN = localStorage.getItem('token')! + await EventsControllerService.joinEvent(p.event.id as number) + p.toggle() + } catch (error) { + console.log(error as ApiError) + p.toggle() + } + } + + const [userIsInRank, setUserIsInRank] = useState(false) + const [currentUser, setCurrentUser] = useState({}) + + // retrieving users rank + useEffect(() => { + if (localStorage.getItem('token')) { + const fetchUsersRank = async () => { + OpenAPI.TOKEN = localStorage.getItem('token')! + try { + const response = await ExSecuredEndpointService.getUserMainPage() + if (response) { + setCurrentUser(response as UserDTO) + } + } catch (error) { + console.error(error as ApiError) + } + } + fetchUsersRank() + } + }, []) + + // checking user's rank + useEffect(() => { + if ( + currentUser.elo! >= p.event.minElo && + currentUser.elo! <= p.event.maxElo + ) { + setUserIsInRank(true) + } else { + setUserIsInRank(false) + } + }, [currentUser, p.event.minElo, p.event.maxElo]) + + const getDateAndTime = (type: string) => { + const dateStart: string[] = p.event.dateStart.split(' ') + if (type === 'date') { + return dateStart[0] + } else if (type === 'time') { + return dateStart[1] + } else { + return null + } + } + + return ( + <> + {userIsInRank ? ( +
+
+
+

Are you sure?

+
+
+
+
+

+ You want to join {p.event.sport} + {' at ' + + p.event.placeDTO?.name + + ' on ' + + getDateAndTime('date') + + ', at ' + + getDateAndTime('time') + + '?'} +

+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ ) : currentUser.name ? ( +
+
+
+

Hang On a Sec!

+
+
+
+

+ Your rank is too low/high to join {p.event.sport} at{' '} + {p.event.placeDTO?.name} +

+
+
+
+
+
+ +
+
+
+ ) : ( +
+
+
+

Hang On a Sec!

+
+
+
+ Please login to join +
+
+
+
+
+ +
+
+
+ )} + + ) +} diff --git a/frontend/sportsmatch-app/src/components/LoadingSpinner.tsx b/frontend/sportsmatch-app/src/components/LoadingSpinner.tsx new file mode 100644 index 00000000..f61d0d92 --- /dev/null +++ b/frontend/sportsmatch-app/src/components/LoadingSpinner.tsx @@ -0,0 +1,26 @@ +import Spinner from 'react-bootstrap/Spinner' + +function LoadingSpinner() { + return ( + <> +
+
+
+
+ + Loading... + +
+
+
+
+
Awaiting Nearby Events
+
+
+
+
+ + ) +} + +export default LoadingSpinner diff --git a/frontend/sportsmatch-app/src/components/LoginComponent.tsx b/frontend/sportsmatch-app/src/components/LoginComponent.tsx new file mode 100644 index 00000000..9bf5ffce --- /dev/null +++ b/frontend/sportsmatch-app/src/components/LoginComponent.tsx @@ -0,0 +1,94 @@ +import { useState, FormEvent } from 'react' +import '../styles/LoginComponent.css' +import { FaMailBulk, FaLock } from 'react-icons/fa' +import { LoginService } from '../generated/api' +import { Link, useLocation, useNavigate } from 'react-router-dom' + +function LoginComponent() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [errorMessage, setErrorMessage] = useState('') + const navigate = useNavigate() + + const location = useLocation() + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() + + try { + const response = await LoginService.login({ + email: email, + password: password, + }) + + console.log(response.data) + localStorage.setItem('token', response.token) + if (location.state != null) { + navigate(location.state) + } else { + navigate('/') + } + } catch (error) { + console.error('Login Error', error) + setErrorMessage('The email address or password is invalid.') + } + } + + return ( +
+
+
+

Log in

+ {errorMessage &&

{errorMessage}

} +
+ + setEmail(e.target.value)} + required + /> + +
+
+ + setPassword(e.target.value)} + required + /> + +
+
+ + {/*Forgot password*/} +
+ +
+

+ Dont have an account REGISTER +

+
+ {/*

Or log in using

*/} + + {/*
*/} + {/*
*/} + {/* */} + {/*
*/} + {/*
*/} + {/* */} + {/*
*/} + {/*
*/} +
+
+
+ ) +} + +export default LoginComponent diff --git a/frontend/sportsmatch-app/src/components/Match.tsx b/frontend/sportsmatch-app/src/components/Match.tsx new file mode 100644 index 00000000..4b6634ee --- /dev/null +++ b/frontend/sportsmatch-app/src/components/Match.tsx @@ -0,0 +1,88 @@ +import '../styles/Match.css' +import { + LuSwords, + LuMapPin, + LuMedal, + LuCalendarCheck, + LuCalendarX, + LuSettings2, +} from 'react-icons/lu' +import { Link } from 'react-router-dom' +import { EventDTO } from '../generated/api/models/EventDTO' +import { useEffect, useState } from 'react' +import { ExSecuredEndpointService, OpenAPI, UserDTO } from '../generated/api' + +interface InProgressProps { + event: EventDTO +} + +function InProgress({ event }: InProgressProps) { + const [currentUser, setCurrentUser] = useState() + useEffect(() => { + OpenAPI.TOKEN = localStorage.getItem('token')! + const fetchUserInfo = async () => { + setCurrentUser( + (await ExSecuredEndpointService.getUserMainPage()) as UserDTO, + ) + } + fetchUserInfo() + }, []) + + return ( + <> +
+
+
+
+ + + + {event.player2Id === null ? ( +

+ Matchmaking +
in progress +

+ ) : ( +

+ Upcoming +
match +

+ )} +
    +
  • + {' '} + {event.player2Id === null + ? 'Awaiting opponent...' + : event.player1Name === currentUser?.name + ? event.player2Name + : event.player1Name} +
  • +
  • + + {event.placeDTO?.name} +
  • +
  • + + {event.minElo} - {event.maxElo} +
  • +
  • + + {event.dateStart} +
  • +
  • + + {event.dateEnd} +
  • +
+
+
+
+
+ + ) +} + +export default InProgress diff --git a/frontend/sportsmatch-app/src/components/Modal.tsx b/frontend/sportsmatch-app/src/components/Modal.tsx new file mode 100644 index 00000000..5a81d1cc --- /dev/null +++ b/frontend/sportsmatch-app/src/components/Modal.tsx @@ -0,0 +1,28 @@ +import { ReactNode } from 'react' +import '../styles/Modal.css' + +interface ModalProps { + children?: ReactNode // React child with content of the popup window + isOpen: boolean // boolean that controlls if modal is displayed + preventClosing: boolean // if false popup window closes after clicking on the overlay + toggle: () => void // a callback function to controll the value of isOpen +} +export default function Modal(p: ModalProps) { + return ( + <> + {!p.preventClosing + ? p.isOpen && ( +
+
e.stopPropagation()}> + {p.children} +
+
+ ) + : p.isOpen && ( +
+
{p.children}
+
+ )} + + ) +} diff --git a/frontend/sportsmatch-app/src/components/Navbar.tsx b/frontend/sportsmatch-app/src/components/Navbar.tsx new file mode 100644 index 00000000..dfbb598a --- /dev/null +++ b/frontend/sportsmatch-app/src/components/Navbar.tsx @@ -0,0 +1,85 @@ +import '../styles/Navbar.css' +import { Link } from 'react-router-dom' +import Avatar from './Avatar' +import { useEffect, useState } from 'react' +import { NavDropdown } from 'react-bootstrap' +import { OpenAPI, ExSecuredEndpointService, ApiError } from '../generated/api' + +function Navbar() { + const loggedInUserImgUrl = '/assets/michael-dam-mEZ3PoFGs_k-unsplash.jpg' + const loggedOutUserImgUrl = '/assets/unknown-user-placeholder.png' + + const [isLoggedIn, setLoggedIn] = useState(false) + const [userId, setUserId] = useState() + + useEffect(() => { + if (localStorage.getItem('token')) { + const init = async () => { + OpenAPI.TOKEN = localStorage.getItem('token')! + try { + const user = await ExSecuredEndpointService.getUserMainPage() + console.log(user) + setUserId(user.id) + setLoggedIn(true) + } catch (error) { + const code = (error as ApiError).status + if (code === 401) { + localStorage.removeItem('token') + setLoggedIn(false) + } + } + } + init() + } + }, []) + + return ( + + ) +} + +export default Navbar diff --git a/frontend/sportsmatch-app/src/components/PrivateRoute.tsx b/frontend/sportsmatch-app/src/components/PrivateRoute.tsx new file mode 100644 index 00000000..9a12b389 --- /dev/null +++ b/frontend/sportsmatch-app/src/components/PrivateRoute.tsx @@ -0,0 +1,40 @@ +import { useState, useEffect } from 'react' +import { Navigate, Outlet } from 'react-router-dom' +import { OpenAPI, ExSecuredEndpointService, ApiError } from '../generated/api' + +const PrivateRoute = () => { + const requestedUrl = window.location.pathname + const [isAuthorized, setAuthorized] = useState(true) + + useEffect(() => { + if (!localStorage.getItem('token')) { + setAuthorized(false) + } + }, []) + + useEffect(() => { + const init = async () => { + if (localStorage.getItem('token')) { + OpenAPI.TOKEN = localStorage.getItem('token')! + try { + await ExSecuredEndpointService.getUserMainPage() + } catch (error) { + const code = (error as ApiError).status + if (code === 401) { + localStorage.removeItem('token') + setAuthorized(false) + } + } + } + } + init() + }, []) + + if (isAuthorized) { + return + } else { + return + } +} + +export default PrivateRoute diff --git a/frontend/sportsmatch-app/src/components/RateGameComponent.tsx b/frontend/sportsmatch-app/src/components/RateGameComponent.tsx new file mode 100644 index 00000000..778b752d --- /dev/null +++ b/frontend/sportsmatch-app/src/components/RateGameComponent.tsx @@ -0,0 +1,218 @@ +import { + ApiError, + EventDTO, + ExSecuredEndpointService, + OpenAPI, + RatingControllerService, + RatingDTO, +} from '../generated/api' +import '../styles/RateGameComponent.css' +import Avatar from './Avatar' +import Rating from './Rating' +import { ChangeEvent, FormEvent, useEffect, useState } from 'react' + +interface Props { + toggle: () => void +} + +export default function RateGameComponent(p: Props) { + const [myEvent, setMyEvent] = useState() + const [opponentName, setOpponentName] = useState('') + + useEffect(() => { + OpenAPI.TOKEN = localStorage.getItem('token')! + const init = async () => { + try { + const response = await ExSecuredEndpointService.getUserMainPage() + const userName = response.name as string + if (myEvent && userName.includes(myEvent.player1Name!)) { + setOpponentName(myEvent.player2Name!) + } else if (myEvent) { + setOpponentName(myEvent.player1Name!) + } + } catch (error) { + console.error(error as ApiError) + } + } + init() + }) + + useEffect(() => { + OpenAPI.TOKEN = localStorage.getItem('token')! + const init = async () => { + try { + const response = await RatingControllerService.checkRating() + setMyEvent(response[0] as EventDTO) + } catch (error) { + const code = (error as ApiError).status + if (code == 401) { + localStorage.removeItem('token') + } + } + } + init() + }, []) + + const userProfilePicture = '/assets/unknown-user-placeholder.png' + const opponentProfilePicture = '/assets/unknown-user-placeholder.png' + + const [userScore, setUserScore] = useState(0) + const [opponentScore, setOpponentScore] = useState(0) + const [userTextRating, setUserTextRating] = useState('') + + const [matchRating, setMatchRating] = useState() + const [opponentRating, setOpponentRating] = useState() + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() + const rating: RatingDTO = { + userTextRating: userTextRating, + userStarRating: opponentRating as number, + eventStarRating: matchRating as number, + myScore: userScore, + opponentScore: opponentScore, + eventId: myEvent!.id!, + } + + try { + await RatingControllerService.addRating(rating) + p.toggle() + } catch (error) { + console.log(error as ApiError) + p.toggle() + } + } + + const handleUserScoreChange = (e: ChangeEvent) => { + const value = parseInt(e.target.value) || 0 + setUserScore(value) + } + const handleOpponentScoreChange = (e: ChangeEvent) => { + const value = parseInt(e.target.value) || 0 + setOpponentScore(value) + } + + const getDateAndTime = (type: string, date: string) => { + const dateStart: string[] = date.split(' ') + if (type === 'date') { + return dateStart[0] + } else if (type === 'time') { + return dateStart[1] + } else { + return null + } + } + + return ( + <> +
+
+

How was your game?

+
+
+
+ {myEvent ? ( + <> + + {'on ' + + getDateAndTime('date', myEvent.dateStart) + + ' at ' + + getDateAndTime('time', myEvent.dateStart) + + ' - ' + + getDateAndTime('time', myEvent.dateEnd)} + +
+ {myEvent.placeDTO?.name}{' '} + + ) : ( +
+ )} +
+
+
+
+

Enter the scores

+
+
+
+
+
+
+ +
+
+
+ +
+
+
:
+
+
+ +
+
+
+ +
+
you
+
{opponentName}
+
+
+
+
+

Was Match Balance

+

Was your opponent nice

+
+
+ setMatchRating(value)} + /> + setOpponentRating(value)} + /> +
+
+
+

+ Your experience with {opponentName} +

+
+
+
+